Related
I just started an MIT course on algorithm, and we were taught the 2D Peak Finding algo. I tried dry running and implementing it yet the algo seems to be failing for this input.
{5, 0, 3, 2}
{1, 1, 2, 4}
{1, 2, 4, 4}
This is the Algorithm:
• Pick middle column j = m/2
• Find global maximum on column j at (i,j)
• Compare(i,j−1),(i,j),(i,j+1)
• Pick left columns of(i,j−1)>(i,j)
• Similarly for right
• (i,j) is a 2D-peak if neither condition holds ← WHY?
• Solve the new problem with half the number of columns.
• When you have a single column, find global maximum and you‘re done.
Update, Here is the code which I tried and doesn't seem to be working:
#include <bits/stdc++.h>
using namespace std;
const int MAX = 100;
int findMax(int arr[][MAX], int rows, int mid, int& max)
{
int max_index = 0;
for (int i = 0; i < rows; i++) {
if (max < arr[i][mid]) {
max = arr[i][mid];
max_index = i;
}
}
return max_index;
}
int findPeakRec(int arr[][MAX], int rows, int columns, int mid)
{
int max = 0;
int max_index = findMax(arr, rows, mid, max);
if (mid == 0 || mid == columns - 1)
return max;
if (max >= arr[max_index][mid - 1] && max >= arr[max_index][mid + 1])
return max;
if (max < arr[max_index][mid - 1])
return findPeakRec(arr, rows, columns, mid - ceil((double)mid / 2));
return findPeakRec(arr, rows, columns, mid + ceil((double)mid / 2));
}
int findPeak(int arr[][MAX], int rows, int columns)
{
return findPeakRec(arr, rows, columns, columns / 2);
}
int main()
{
int arr[][MAX] = { { 5, 0, 3, 2 },
{ 1, 1, 2, 4 },
{ 1, 2, 4, 4 },
{ 3, 2, 0, 1 } };
int rows = 4, columns = 4;
cout << findPeak(arr, rows, columns);
return 0;
}
this is how I implemented the algorithm.
The algorithm is correct (Just a spelling mistake in the fourth bullet point: "of" should read "if").
You missed a correct definition of "peak". The algorithm for peak finding intends to find a local maximum, not necessarily the global maximum. For a global maximum the algorithm is trivial, you just look for the maximum value with a row by row scan.
But peak finding can be more efficient as not all values need to be inspected.
Interview question: Given an array where any two consecutive elements differ in their values by 1
example:
vector<int> vec = { 1, 0, -1, -2, -3, -4,-3,-2,-1,0,1, 2, 1, 2, 3 };
index==>0, 1, 2, 3, 4, 5, 6, 7, 8,9,10,11,12,13,14
The aim is to search an element K in this array in less than O(n).
My attempt:
start from index 0 . we can skip some indexes. Since elements differ by 1 and we need to search for k , lets insect elements and see a range in between which element could be found.
index = 0
The max value I can predict will be at a[idx + k] and min value at a[idx -k] as at each value differ by 1 .. however, this does not lead to anywhere
EDIT:
Code tried for suggestion given in an answer
#include "stdafx.h"
#include "vector"
#include "iostream"
using namespace std;
int closeS(vector<int> & vec, int search , int& hopsTaken)
{
int idx = 0;
while (idx < vec.size() && vec[idx] != search)
{
idx += abs (search - vec[idx]);
++hopsTaken;
}
if (idx < vec.size())
{
cout << idx <<"\n";
return idx;
}
return -1;
}
int main()
{
int hopsTaken = 0;
vector<int> vec = { 1,0,-1,-2,-3,-4,-3,-2,-1,0,1,2,1,2,3 };
cout << closeS(vec, 3, hopsTaken); // , 0, vec.size() - 1)];
cout << " \n hopsTaken " << hopsTaken <<" in array size" << vec.size() <<" for k = " << 3 <<"\n";
int y;
cin >> y;
return 0;
}
Tried few items and it was always <= O(n/k)
Still searching for better as its still O(n)
Begin at the first index and jump by the difference to the searched Element:
Eg Search for 2: Begin at index 0
0, vec[0]=1, 2-1=1 => nextindex 0+1=1
1, vec[1]=0, 2-0=2 => 1+2=3
3, vec[3]=-2, 2--2=4 => 3+4=7
7, vec[7]=-2, 2--2=4 => 7+4=11
11, vec[11]=2
Eg Search for 3: Begin at index 0
0, vec[0]=1, 3-1=2 => 0+2=2
2, vec[2]=-1, 3--1=4 => 2+4=6
6, vec[6]=-3, 3--3=6 => 6+6=12
12, vec[12]=1, 3-1=2 => 12+2=14
14, vec[11]=3
Given an array A of n integers, I want to find the ways of selecting ordered triplets. For eg.
A = [1, 2, 1, 1]
different ways are (1, 2, 1), (1, 1, 1) and (2, 1, 1)
so the answer will be 3.
for A = [2, 2, 1, 2, 2]
different ways are (1, 2, 2), (2, 1, 2), (2, 2, 1) and (2, 2, 2)
so the answer will be 4 in this case
If all the numbers are unique then I have come up with a recurrence
f(n) = f(n-1) + ((n-1) * (n-2))/2
where f(3) = 1 and f(2) = f(1) = 0
I am having trouble when numbers are repeated. This needs to be solved in O(n) time and O(n) space.
The dynamic programming relation for the number of unique, ordered sets, from an array of size idx is:
DP[size of set][idx] = DP[size of set][idx-1] + DP[size of set - 1][idx-1] - DP[size of set - 1][ last_idx[ A[idx] - 1]
So, to calculate the number of ordered, unique sets of size LEN from an array of idx elements:
Take the number of ordered, unique sets of size LEN that can be created from an array of idx-1 elements
Add the number of ordered, unique sets that can be formed by adding element idx to the end of ordered, unique sets for size LEN-1
Don’t double count. Subtract the number of ordered, unique sets that can be formed by adding the PREVIOUS occurrence of element idx to the end of ordered, unique sets for size LEN-1.
This works because we are always counting unique sets as we go through the array. Counting unique the sets is based on the previous element counts of unique sets.
So, start with sets of size 1, then do size 2, then size 3, etc.
For unique, ordered sets of constant size LEN, my function takes O(LEN * N) memory and O(LEN * N) time. You should be able to reuse the DP array to reduce the memory to a constant independent of LEN, O(constant * N).
Here is the function.
static int answer(int[] A) {
// This example is for 0 <= A[i] <= 9. For an array of arbitrary integers, use a proper
// HashMap instead of an array as a HashMap. Alternatively, one could compress the input array
// down to distinct, consecutive numbers. Either way max memory of the last_idx array is O(n).
// This is left as an exercise to the reader.
final int MAX_INT_DIGIT = 10;
final int SUBSEQUENCE_LENGTH = 3;
int n = A.length;
int[][] dp = new int[SUBSEQUENCE_LENGTH][n];
int[] last_idx = new int[MAX_INT_DIGIT];
Arrays.fill(last_idx, -1);
// Init dp[0] which gives the number of distinct sets of length 1 ending at index i
dp[0][0] = 1;
last_idx[A[0]] = 0;
for (int i = 1; i < n; i++) {
if (last_idx[A[i]] == -1) {
dp[0][i] = dp[0][i - 1] + 1;
} else {
dp[0][i] = dp[0][i - 1];
}
last_idx[A[i]] = i;
}
for (int ss_len = 1; ss_len < SUBSEQUENCE_LENGTH; ss_len++) {
Arrays.fill(last_idx, -1);
last_idx[A[0]] = 0;
for (int i = 1; i < n; i++) {
if (last_idx[A[i]] <= 0) {
dp[ss_len][i] = dp[ss_len][i - 1] + dp[ss_len-1][i - 1];
} else {
dp[ss_len][i] = dp[ss_len][i - 1] + dp[ss_len-1][i - 1] - dp[ss_len-1][last_idx[A[i]] - 1];
}
last_idx[A[i]] = (i);
}
}
return dp[SUBSEQUENCE_LENGTH-1][n - 1];
}
For [3 1 1 3 8 0 5 8 9 0] the answer I get is 62.
Given array : 8 3 5 2 10 6 7 9 5 2
So the o/p will be Yes.
as: {8,3,5} {10,6} {9,5,2} they all have same sum value i.e. 16.
But for this array : 1 4 9 6 2 12
o/p will be No.
as: No contiguous slide have same sum value
I was thinking to go with SubSetSum Algorithm / Kadane Maximum SubArray Algorithm but later I end up as all of the algorithms requires a target sum which is predefined.
But here we don't know the target sum
If desired sum is given, and all subarrays should be contiguous, then it's easily can be done in O(n).
Run a loop over array and maintain boundaries of slices (left and right indexes) and currentSum.
Start with first element as a 0. Boundaries will be [0, 0] (for simplicity we include right). Then in a loop you have three conditions.
If sum is less than desired, add right element to the sum and advance right index
If sum is greater than desired, remove left element from the sum and advance left index
If sum is equal to given, print the slice. To avoid this slice in next iteration, advance left index and adjust the sum.
Translated to code
public static void main(String[] args) {
int givenSum = 16;
int[] a = new int[] {8, 3, 5, 2, 10, 6, 7, 9, 5, 2};
// boundaries of slice
int left = 0; // defines position of slice
int right = 0; // exclusive
int currentSum = 0;
while (right < a.length) {
if (currentSum < givenSum) { // sum is not enough, add from the right
currentSum += a[right];
right++;
}
if (currentSum > givenSum) { // sum exceeds given, remove from the left
currentSum -= a[left];
left++;
}
if (currentSum == givenSum) { // boundaries of given sum found, print it
System.out.println(Arrays.toString(Arrays.copyOfRange(a, left, right)));
// remove the left element, so we can process next sums
currentSum -= a[left];
left++;
}
}
}
For your case it prints 4 slices which yields sum 16
[8, 3, 5]
[10, 6]
[7, 9]
[9, 5, 2]
EDIT:
As OP clarified, no given sum available, the goal is to check if there are at least two different contiguous subarrays present which yields equal sum.
The most straightforward algorithm is to generate all possible sums and check if there are duplicates
int[] a = new int[] {1, 4, 9, 6, 2, 12};
HashSet<Integer> sums = new HashSet<>();
int numOfSums = 0;
for (int left = 0; left < a.length - 1; left++) {
for (int right = left; right < a.length; right++) {
// sum from left to right
int sum = 0;
for (int k = left; k <= right; k++) {
sum += a[k];
}
numOfSums++;
sums.add(sum);
}
}
System.out.println(sums.size() == numOfSums);
Complexity of this is O(n^3), not a good one, but works.
Hint: One trick could be explored to boost it to O(n^2), you don't need to calculate sum for every pair of slices!
You can do it in the following way
You have the total sum = 48
Now the each subset would have a sum which would be equal to a factor of 48. The smaller the factor the more number of subsets you can break it into
For all factors of the sum, check if the answer is possible for that factor or not. This can be done in O(n) by simply traversing the array.
Time Complexity would be O(n * factors(sum))
Use dynamic programming to find all sub-sums of the array, then find the sub array with same sum. The complexity should be O(n2).
void subsum(int n, int* arr, int** sum) {
for (int i = 0; i < n; ++i) {
sum[i][i] = arr[i];
}
for (int l = 2; l <= n; ++l) {
for (int i = 0; i < n - l + 1; ++i) {
sum[i][i + l - 1] = sum[i][i + l - 2] + arr[i + l -1];
}
}
}
LIS:wikipedia
There is one thing that I can't understand:
why is X[M[i]] a non-decreasing sequence?
Let's first look at the n^2 algorithm:
dp[0] = 1;
for( int i = 1; i < len; i++ ) {
dp[i] = 1;
for( int j = 0; j < i; j++ ) {
if( array[i] > array[j] ) {
if( dp[i] < dp[j]+1 ) {
dp[i] = dp[j]+1;
}
}
}
}
Now the improvement happens at the second loop, basically, you can improve the speed by using binary search. Besides the array dp[], let's have another array c[], c is pretty special, c[i] means: the minimum value of the last element of the longest increasing sequence whose length is i.
sz = 1;
c[1] = array[0]; /*at this point, the minimum value of the last element of the size 1 increasing sequence must be array[0]*/
dp[0] = 1;
for( int i = 1; i < len; i++ ) {
if( array[i] < c[1] ) {
c[1] = array[i]; /*you have to update the minimum value right now*/
dp[i] = 1;
}
else if( array[i] > c[sz] ) {
c[sz+1] = array[i];
dp[i] = sz+1;
sz++;
}
else {
int k = binary_search( c, sz, array[i] ); /*you want to find k so that c[k-1]<array[i]<c[k]*/
c[k] = array[i];
dp[i] = k;
}
}
This is the O(n*lg(n)) solution from The Hitchhiker’s Guide to the Programming Contests (note: this implementation assumes there are no duplicates in the list):
set<int> st;
set<int>::iterator it;
st.clear();
for(i=0; i<n; i++) {
st.insert(array[i]);
it=st.find(array[i]);
it++;
if(it!=st.end()) st.erase(it);
}
cout<<st.size()<<endl;
To account for duplicates one could check, for example, if the number is already in the set. If it is, ignore the number, otherwise carry on using the same method as before. Alternatively, one could reverse the order of the operations: first remove, then insert. The code below implements this behaviour:
set<int> st;
set<int>::iterator it;
st.clear();
for(int i=0; i<n; i++) {
it = st.lower_bound(a[i]);
if (it != st.end()) st.erase(it);
st.insert(a[i]);
}
cout<<st.size()<<endl;
The second algorithm could be extended to find the longest increasing subsequence(LIS) itself by maintaining a parent array which contains the position of the previous element of the LIS in the original array.
typedef pair<int, int> IndexValue;
struct IndexValueCompare{
inline bool operator() (const IndexValue &one, const IndexValue &another){
return one.second < another.second;
}
};
vector<int> LIS(const vector<int> &sequence){
vector<int> parent(sequence.size());
set<IndexValue, IndexValueCompare> s;
for(int i = 0; i < sequence.size(); ++i){
IndexValue iv(i, sequence[i]);
if(i == 0){
s.insert(iv);
continue;
}
auto index = s.lower_bound(iv);
if(index != s.end()){
if(sequence[i] < sequence[index->first]){
if(index != s.begin()) {
parent[i] = (--index)->first;
index++;
}
s.erase(index);
}
} else{
parent[i] = s.rbegin()->first;
}
s.insert(iv);
}
vector<int> result(s.size());
int index = s.rbegin()->first;
for(auto iter = s.rbegin(); iter != s.rend(); index = parent[index], ++iter){
result[distance(iter, s.rend()) - 1] = sequence[index];
}
return result;
}
We need to maintain lists of increasing sequences.
In general, we have set of active lists of varying length. We are adding an element A[i] to these lists. We scan the lists (for end elements) in decreasing order of their length. We will verify the end elements of all the lists to find a list whose end element is smaller than A[i] (floor value).
Our strategy determined by the following conditions,
1. If A[i] is smallest among all end candidates of active lists, we will start new active list of length 1.
2. If A[i] is largest among all end candidates of active lists, we will clone the largest active list, and extend it by A[i].
3. If A[i] is in between, we will find a list with largest end element that is smaller than A[i]. Clone and extend this list by A[i]. We will discard all other lists of same length as that of this modified list.
Note that at any instance during our construction of active lists, the following condition is maintained.
“end element of smaller list is smaller than end elements of larger lists”.
It will be clear with an example, let us take example from wiki :
{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}.
A[0] = 0. Case 1. There are no active lists, create one.
0.
-----------------------------------------------------------------------------
A[1] = 8. Case 2. Clone and extend.
0.
0, 8.
-----------------------------------------------------------------------------
A[2] = 4. Case 3. Clone, extend and discard.
0.
0, 4.
0, 8. Discarded
-----------------------------------------------------------------------------
A[3] = 12. Case 2. Clone and extend.
0.
0, 4.
0, 4, 12.
-----------------------------------------------------------------------------
A[4] = 2. Case 3. Clone, extend and discard.
0.
0, 2.
0, 4. Discarded.
0, 4, 12.
-----------------------------------------------------------------------------
A[5] = 10. Case 3. Clone, extend and discard.
0.
0, 2.
0, 2, 10.
0, 4, 12. Discarded.
-----------------------------------------------------------------------------
A[6] = 6. Case 3. Clone, extend and discard.
0.
0, 2.
0, 2, 6.
0, 2, 10. Discarded.
-----------------------------------------------------------------------------
A[7] = 14. Case 2. Clone and extend.
0.
0, 2.
0, 2, 6.
0, 2, 6, 14.
-----------------------------------------------------------------------------
A[8] = 1. Case 3. Clone, extend and discard.
0.
0, 1.
0, 2. Discarded.
0, 2, 6.
0, 2, 6, 14.
-----------------------------------------------------------------------------
A[9] = 9. Case 3. Clone, extend and discard.
0.
0, 1.
0, 2, 6.
0, 2, 6, 9.
0, 2, 6, 14. Discarded.
-----------------------------------------------------------------------------
A[10] = 5. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 5.
0, 2, 6. Discarded.
0, 2, 6, 9.
-----------------------------------------------------------------------------
A[11] = 13. Case 2. Clone and extend.
0.
0, 1.
0, 1, 5.
0, 2, 6, 9.
0, 2, 6, 9, 13.
-----------------------------------------------------------------------------
A[12] = 3. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 1, 5. Discarded.
0, 2, 6, 9.
0, 2, 6, 9, 13.
-----------------------------------------------------------------------------
A[13] = 11. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 2, 6, 9.
0, 2, 6, 9, 11.
0, 2, 6, 9, 13. Discarded.
-----------------------------------------------------------------------------
A[14] = 7. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 1, 3, 7.
0, 2, 6, 9. Discarded.
0, 2, 6, 9, 11.
----------------------------------------------------------------------------
A[15] = 15. Case 2. Clone and extend.
0.
0, 1.
0, 1, 3.
0, 1, 3, 7.
0, 2, 6, 9, 11.
0, 2, 6, 9, 11, 15. <-- LIS List
Also, ensure we have maintained the condition, “end element of smaller list is smaller than end elements of larger lists“.
This algorithm is called Patience Sorting.
http://en.wikipedia.org/wiki/Patience_sorting
So, pick a suit from deck of cards. Find the longest increasing sub-sequence of cards from the shuffled suit. You will never forget the approach.
Complexity : O(NlogN)
Source: http://www.geeksforgeeks.org/longest-monotonically-increasing-subsequence-size-n-log-n/
i came up with this
set<int> my_set;
set<int>::iterator it;
vector <int> out;
out.clear();
my_set.clear();
for(int i = 1; i <= n; i++) {
my_set.insert(a[i]);
it = my_set.find(a[i]);
it++;
if(it != my_set.end())
st.erase(it);
else
out.push_back(*it);
}
cout<< out.size();
You cannot understand, because the code in wikipedia is wrong(I strongly believe so). It is not only wrong but the variables are poorly named. But it allowed me to spend time to understand how it works :D.
Now after I read the patience-sort. I rewrote the algorithm. I also wrote the corrected binary search.
Patience sort is like Insertion sort
Like insertion sort, patience-sort finds appropriate place for the next item by doing binary search. The binary search is done on the card-piles built in sorted order. Let me assign a variable for the card-pile.(I am talking about playing cards because patience is a simplified card game).
//! card piles contain pile of cards, nth pile contains n cards.
int top_card_list[n+1];
for(int i = 0; i <= n; i++) {
top_card_list[i] = -1;
}
Now the top_card_list contains the top-card of the card pile of height n. Patience sort places the card in hand over the highest top-card that is smaller than it(or the opposite). For further sorting note, please refer to wikipedia page for patience sort.
3
* 7 2
-------------------------------------------------------------
Pile of cards above (top card is larger than lower cards)
(note that pile of card represents longest increasing subsequence too !)
Binary search on pile of cards
Now to find a number while we do dynamic programming for longest-increasing subsequence, we run an inner loop which is O(n).
for(int i = 1; i < n; i++) { // outer loop
for(int j = 0; j < i; j++) { // inner loop
if(arr[i] > arr[j]) {
if(memo_len[i] < (memo_len[j]+1)) {
// relaxation
memo_len[i] = memo_len[j]+1;
result = std::max(result,memo_len[i]);
pred[i] = j;
}
}
}
}
And the inner-loop is there to find the highest-top card that is smaller than our card in hand.
But we know that we can do it by binary search ! (exercise: prove the correctness) In that way we can do that in O(log (number of piles)) time. Now O(number of piles) = O(number of cards)(but number of card is 52, it should be O(1)!, just joking!). So the total application runs in O(n log n) time.
Here is the revised the DP with binary search.
for(int i = 1; i < n; i++) {
pile_height[i] = 1;
const int j = pile_search(top_card_list, arr, pile_len, arr[i]);
if(arr[i] > arr[j]) {
if(pile_height[i] < (pile_height[j]+1)) {
// relaxation
pile_height[i] = pile_height[j]+1;
result = std::max(result,pile_height[i]);
pile_len = std::max(pile_len,pile_height[i]);
}
}
if(-1 == top_card_list[pile_height[i]] || arr[top_card_list[pile_height[i]]] > arr[i]) {
top_card_list[pile_height[i]] = i; // top card on the pile is now i
}
}
Here is the correct pile search below. It is simply a binary search, but it finds the index of the top-card which is smaller than card in hand.
inline static int pile_search(const int*top_card_list, const vector<int>& arr, int pile_len, int strict_upper_limit) {
int start = 1,bound=pile_len;
while(start < bound) {
if(arr[top_card_list[bound]] < strict_upper_limit) {
return top_card_list[bound];
}
int mid = (start+bound)/2 + ((start+bound)&1);
if(arr[top_card_list[mid]] >= strict_upper_limit) {
// go lower
bound = mid-1;
} else {
start = mid;
}
}
return top_card_list[bound];
}
Notice that unlike wikipedia, it returns top_card_list[bound] (my fix). Also notice where the top_card_list[] is updated in the dp. This code is tested for the boundary cases. I hope it helps.
There is a proof here https://strncat.github.io/jekyll/update/2019/06/25/longest-increasing-subsequence.html
basically it is impossible to not be a strictly increasing subsequence.
The proof is by contradiction: Suppose it is not then we have two cases:
Case 1) There is some element M[j] that ends two subsequences of length j and j+some number. This is impossible (proof in link)
Case 2) Slightly different that Case 1 but pretty the same reasoning. How can you have a smallest number end two subsequences of two different lengths? it can't be.
The base idea behind algorithm is to keep list of LIS of a given length ending with smallest possible element. Constructing such sequence
Find immediate predecessor in already known last elements sequence ( lets say its of length k)
Try to append current element to this sequence and build new better solution for k+1 length
Because in first step you search for smaller value then X[i] the new solution (for k+1) will have last element greater then shorter sequence.
I hope it will help.
You can surely check this video for explanation:
https://www.youtube.com/watch?v=nf3YG4CnTbg&feature=youtu.be
My code for nlogn approch is:
int n;
cin>>n;//LENGTH OF ARRAY
vector<int>v(n);
for(int i=0;i<n;i++){
cin>>v[i];
}
vector<int>d(n+1,INT_MAX);//AUXILLARY ARRAY
for(int i=0;i<=n;i++){
*lower_bound(d.begin(),d.end(),v[i])=v[i];
}
for(int i=0;i<n;i++){
if(d[i]==INT_MAX){
cout<<i;//LENGTH OF LIS
exit(0);
}
}