Interview Question for SDE1s


Country: United States
Interview Type: In-Person




Comment hidden because of low score. Click to expand.
2
of 2 vote

Using Deque can solve this problem in O(N) time

deque is a queue that can be pushed/popped from both ends. We use a deque DQ to store the elements for the sliding K-window. The trick is that, we only store the elements that are potentially be the maximum of future K-windows. So, if we see a number x that is greater than all elements in the current deque, we know for sure that x now will be the max, and all those elements in current deque will never be the max again. Thus, they must be removed, replacing by x.

Example:
A = [8, 5, 10, 7,9, 4, 1, 1, 1…]
We see 8: nothing to compare
When we see 5: we know that when 8 is out of the future K-window, 5 can potentially be the max of that window. We need to store 5

When we see 10: we know that 8, 5 can never be the max for future K-window. Thus, 8,5 should be removed. The deque now contains only 10

When we see 7: 7 can potentially be the max when 10 is out. Store 7, deque becomes [10, 7]

When we see 9: 7 won’t be the max anymore, remove 7. Store 9, deque becomes [10,9]

And so on..

EDITED: I explained it here:
capacode. com/?p=1042

- ninhnnsoc January 23, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 votes

doubtfully.
could you please provide the solution?

- zr.roman January 23, 2016 | Flag
Comment hidden because of low score. Click to expand.
1
of 1 vote

Here is the working code in python:

A = [8, 5, 10, 7, 9, 4, 15, 12, 90, 13] 
A = [9,8,7,6,5,4,3,2,1,0]
N = len(A)
K = 4

from collections import deque
DQ = deque()    # store the index of numbers within the window of size K

RES = []
for i in xrange(N):
    print i, A[i], DQ, RES
    while len(DQ)>0 and A[DQ[-1]] <= A[i]: # Once a new max is encountered, all previous max are useless, thus need to remove
	    DQ.pop()
    while len(DQ)>0 and DQ[0] <= i-K:    # Remove index outside the K-window
        DQ.popleft()
    DQ.append(i)
    if i>=K-1:
        RES.append(A[DQ[0]])

print RES
#[10, 10, 10, 15, 15, 90, 90]

This runs in O(N) time because each element is added and removed from the deque at most once.

EDITED: fixed the bug, after zr.roman's comment.

- ninhnnsoc January 23, 2016 | Flag
Comment hidden because of low score. Click to expand.
2
of 2 votes

does not work on input [9,8,7,6,5,4,3,2,1,0].
It gives [9, 9, 8, 7, 6, 5, 4], while it should be [ 9, 8, 7, 6, 5, 4, 3].

- zr.roman January 23, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

@zr.roman: Thanks for testing.
Change "DQ[0] < i-K" into "DQ[0] <= i-K" should work.

- ninhnnsoc January 23, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

does not work on input [9,1,3,2,5,4,3,2,1,0].
It gives [9, 3, 3, 5, 5, 4, 3], while it should be [ 9, 5, 5, 5, 5, 4, 3].

- zr.roman January 23, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

@zr.roman: It gave [ 9, 5, 5, 5, 5, 4, 3] as expected.

- ninhnnsoc January 24, 2016 | Flag
Comment hidden because of low score. Click to expand.
1
of 1 vote

sorry, that was my fault (I replaced "while" by "if", to get ensured in time complexity).
So, solution works, but it is not O(n) solution, It is O(n*k) solution.
Inside the outer "for" loop you have inner "while" loop, which searches for new max val.
In case of input [9,1,3,2,5,4,3,2,1,0] at the 5th iteration the inner "while" loop performs 2 times, this is order k running time (in this case the loop was lucky and found the value in 2 iterations, but in worst case when it is unlucky it will take order k time).
Take another example: [9,1,3,2,5,1,3,2,4,0].
Here the inner loop is performed twice in 5th and 9th "for" loop iterations.
And so on.
Obviously, this is not linear time, because we cannot eliminate the cost of inner loop.
So, asymptotically, taking into account worst case, this is O(n*k) time complexity.

- zr.roman January 24, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 2 votes

@zr.roman: Thanks for looking into it!

If we look more carefully with the while loop, we will see that it's not always run in O(K) time.

Analyse by this way will give a tighter bound O(N):
Each element of the array is scanned once. It is added into the deque at most once. And it is removed at most once. So, altogether, the algorithms will take O(N) time.

This is amortized/aggregate analysis.

- ninhnnsoc January 24, 2016 | Flag
Comment hidden because of low score. Click to expand.
-1
of 1 vote

thanks for an interesting case and discussion.
Now I catch your idea.
I was wrong, you were right.
More careful analysis really gives O(n) time complexity due to the idea of tracking possible max vals candidates for each next k-window.
Nice example!
But one observation: actually, deque is not necessary here.
We can use simple ArrayList.
In this case we eliminate inner loops, but though we get RemoveAt(0) operation, which is O(k), but performed rarely, so tighter bound again O(N).
c# implementation.

private static int[] Get1( int[] arr, int k ) {

    int[] res = new int [ arr.Length - k + 1 ];
    List<int> list  = new List<int> { 0 };

    for ( int i = 1; i < arr.Length; i++ ) {
                                
        if ( list.Count == 0 || arr[ i ] > arr[ list[ 0 ] ] ) {
            list = new List<int> { i };
        }
                                
        if ( arr[ i ] < arr[ list[ 0 ] ] ) {
            var target = arr[ list[ list.Count - 1 ] ];
            if ( arr[ i ] > target ) {
                list.RemoveAt( list.Count - 1 );
            }
            list.Add( i );
        }

        if ( i >= k - 1 ) {
            res[ i - k + 1 ] = arr[ list[ 0 ] ];
            if ( arr[ i - k + 1 ] == arr[ list[ 0 ] ] ) {
                list.RemoveAt( 0 ); // here we get O(k) complexity, but in rare cases
                                    // so, overall complexity remains order O(n)
            }
        }
    }
    return res;
}

- zr.roman January 24, 2016 | Flag
Comment hidden because of low score. Click to expand.
1
of 1 vote

I forget to mention that, push/pop from left/right of deque are O(1) time operations, thus the algorithm is O(N) time.

The idea of deque is just an abstraction, we can implement it in different ways. For example, it can be implemented as a doubly linked list.

For java ArrayList:
If RemoveAt(0) is O(K) time, then it's not good. Imagine the case when K = N/2, and numbers are sorted in decreasing order. This case will take O(N.K), which is O(N^2) time.

- ninhnnsoc January 24, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

right, the main is the idea, implementation can be different.
For ArrayList we can do O(n) solution by introducing moving index (as shown below in solution), though we get O(n) space complexity.

- zr.roman January 24, 2016 | Flag
Comment hidden because of low score. Click to expand.
1
of 1 vote

There is a recurrent formula - dp[i] = Math.max(nums[i+k-1], dp[i-1]), where dp contains all our problem states (dp[i] is the max element in subarray with length k that start at index i).

{
  void findMaxKSubArray(int[] nums, int k) {
	  if (nums.length < k) {
		  throw new IllegalArgumentException();
	  }
	  int dp[] = new int[nums.length - k + 1];
	  int max = Integer.MIN_VALUE;
	  for (int i = 0; i < k; i++) {
		  max = Math.max(max, nums[i]);
	  }
	  dp[0] = max;
	  for (int i = 1; i <= nums.length - k; i++) {
		  dp[i] = Math.max(nums[i+k-1], dp[i-1]);
	  }
	  for (int i = 0; i <= nums.length - k; i++) {
		  System.out.print(dp[i]+ " ");
	  }  
  }

- EPavlova January 22, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 votes

Nums = 7,3,1
for (int i = 0; i < k; i++) {
max = Math.max(max, nums[i]);
}
Max (7, 3)
dp[0] <-7
dp[1] = max( nums[2] , dp[0])
dp[1] = max(1, 7 )
dp[1] <- 7
it should be
max (3,1)
3
Or at least that is how a parse the question

- DR A.D.M January 22, 2016 | Flag
Comment hidden because of low score. Click to expand.
1
of 1 vote

On input {9,8,7,6,5,4,3,2,1} and k = 4 the expected output is {9,8,7,6,5,4} but it gives {9,9,9,9,9,9}.
What is the logic? is that correct? can you explain it please?

- zr.roman January 22, 2016 | Flag
Comment hidden because of low score. Click to expand.
1
of 1 vote

I see the mistake: dp[i] = Math.max(nums[i+k-1], dp[i-1]) - this is not quite true.
Assume, we have array {9,8,7,6,5} and k = 4, so on the 2nd step (i = 1) we'll have nums[i+k-1] = 5 and dp[i-1] = 9, but this condition should lead to recalculating the max val, because the previous max val (9) lays outside the current subarray {8,7,6,5}.

- zr.roman January 23, 2016 | Flag
Comment hidden because of low score. Click to expand.
1
of 1 vote

I dont see this specific as a DP problem until and explicitly told to solve it in DP..

DP Algo As follows.

void Solution(int []array)
{
for(int i = 0; i < (array.length - k) l i++)
{
print 'findMax(i , i +k , array)';
}

}

int findMax(int start, int end, int []array)
{
// use careful data structure like priority queue to get the max in O(1) time.
}

- hprem991 January 23, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
1
of 1 vote

O(n) solution without deque.
c# implementation.
Edited after ninhnnsoc's comment.

private static int[] Get1( int[] arr, int k ) {

    int t = 0;
    int[] res = new int [ arr.Length - k + 1 ];
    List<int> list  = new List<int> { 0 };

    for ( int i = 1; i < arr.Length; i++ ) {
                                
        if ( list.Count == 0 || arr[ i ] > arr[ list[ t ] ] ) {
            list = new List<int> { i };
            t = 0;
        }
        else                
        if ( arr[ i ] < arr[ list[ t ] ] ) {
            var target = arr[ list[ list.Count - 1 ] ];
            if ( arr[ i ] > target ) {
                list = new List<int> { list[ 0 ] };                        
            }
            list.Add( i );
        }

        if ( i >= k - 1 ) {
            res[ i - k + 1 ] = arr[ list[ t ] ];
            if ( arr[ i - k + 1 ] == arr[ list[ t ] ] ) {
                t++;
            }
        }
    }
    return res;
}

- zr.roman January 24, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 votes

Should use deque or doubly linked list. Java ArrayList doesn't support RemoveAt(0) well.

One worst-case input is: A = [100000, 99999, 99998, ..., 1], K = 100000/2

Nice try anyway!

- ninhnnsoc January 24, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

thanks for attentive investigation of my code!
Drawbacks of RemoveAt(0) operation can be eliminated by introducing index, which points to the logical first element in ArrayList (improved in code).
This will be O(n) time solution.
Though we get O(n) space complexity against O(k) in case of deque.

- zr.roman January 24, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 vote

Without dynamic programming just using a couple of nested loops you end up with something that is O(nk) in complexity
You could use this relationship to do dynamic programming
F(i,k) = max (F(i,k-1), A[i+k])
Where i is an index and A is the array
But this is not very satisfying since you are still at O(nk)
Using this relation
F(i,k) = max(F(i, floor(k/2)), F(i, ceiling (k/2)))
This gets you down to O(n*log(k))
memoization would be good at this point an make for a simple implementation.
But let's see if we can do it bottom up.
Write a recursive program to compute the values of k we need. If we compute all maxes for k we will compute a lot of values we don’t need.
We will need to store these values of k into some fancy data structure which denotes the order of computation for the different table lengths if hearts the brain just to think about it.
For the value k = 51 you need k = 25 and k = 26
For k = 25 you need 12 and 13
For 26 you need 13
For 12 you need 6
For 13 you need 7 and 6
For 7 you need 4 and 3
For 4 you need 2
For 3 you need 2 as well
You can compute 2 then 3 and 4 then 6 and 7 then 12 and 13 then 25 and 26 then 51
It is a pain
So try a different approach
Store the first k items in a balanced binary search tree that has O(log(k)) complexity
Store the biggest one that is the max of the first k elements in your table as element 0
Now remove the first one from the tree. It will take O(log(k)) to do this
Now add the next element A[k+1]
that will take another O(log(k))
Store the biggest one that is the max of the next k elements.
Yet another O(log(k))
I don’t think this counts as dynamic programming but it is O(n*log(k)) time complexity
And you only need enough extra memory to store the tree for k elements.

- Dr A.D.M. January 22, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 vote

Nums = 7,3,1
for (int i = 0; i < k; i++) {
max = Math.max(max, nums[i]);
}
Max (7, 3)
dp[0] <-7
dp[1] = max( nums[2] , dp[0])
dp[1] = max(1, 7 )
dp[1] <- 7
it should be
max (3,1)
3
Or at least that is how a parse the question

- Anonymous January 22, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 vote

void maxOfEachSubArrayofSizeK(int iArr[N],int k)
{
	int max=0,j=0,iCnt=0;
	int iMaxValue[n];
	max = iArr[0];
	for(int i=0;i<N;i++)
	{
		if(iCnt ==k)
		{
			iMaxValue[j] = max;
			iCnt = 0;
			max=0;
			j++;
		}
		if(max < iArr[i])
			max = iArr[i];
		iCnt++;
	}
	iMaxValue[j] = max;
}

- sv January 22, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 vote

the task is a bit unclear.
Could the author please specify via example the needed output?
Say, given array {9,8,7,6,5,4,3,2,1,0} and k = 4.
What should be the output?

- zr.roman January 22, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 votes

Hi zr.roman, I think you are able to think
Examples:
Input :
arr[] = {1, 2, 3, 1, 4, 5, 2, 3, 6}
k = 3
Output :
3 3 4 5 5 5 6

Input :
arr[] = {8, 5, 10, 7, 9, 4, 15, 12, 90, 13}
k = 4
Output :
10 10 10 15 15 90 90

- HimanshuP January 23, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 votes

sorry for disturbing!
I guessed it exactly as you describe here and even implemented a solution (posted here).
Misunderstanding was caused by the word "contiGuous". I suppose the correct word for the task is "contiNuous". (according to the idea of the task).
Thanks for examples anyway.

- zr.roman January 23, 2016 | Flag
Comment hidden because of low score. Click to expand.
0
of 0 vote

c# implementation.

using System;

namespace MaxContiguousArrays{

    class Program {

        private static int[] Get( int[] arr, int k ) {

            int[] dp = new int [ arr.Length - k + 1 ];
            dp[ 0 ] = Math.Max( arr[ k - 1 ], GetMaxInSubArray( arr, 0, k - 1 ) );

            for ( int i = 1; i < arr.Length; i++ ) {
                var iAhead = i + k - 1;
                if ( iAhead >= arr.Length ) { break; }
                dp[ i ] = arr[ iAhead ] > dp[ i - 1 ]
                          ? arr[ iAhead ]
                          : Math.Max( arr[ iAhead ], GetMaxInSubArray( arr, i, k - 1 ) );
            }
            return dp;
        }

        private static int GetMaxInSubArray( int[] arr, int start, int k ) {

            int maxVal = arr[ start ];
            for ( int i = start; i < start + k; i++ ) {
                if ( arr[ i ] > maxVal ) {
                    maxVal = arr[ i ];
                }
            }
            return maxVal;
        }

        static void Main(string[] args)
        {
            var res = Get(new int[] { 9,8,7,6,5,4,3,2,1,0 }, 4);
        }
    }
}

- zr.roman January 22, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 vote

It is the same question as "find out maximum in the window of size K in an array." To answer this you have to use the priority queue and such. Basically here what we have to do is this:
Use the queue for this and its size should be equal "K".
1. Put the first element in the queue.
2. Put the second element in the queue but before doing that compare its value to the top of the queue and see if its value is greater. If it is, then remove the top of the queue until the top of the queue is no longer more that the element which you are going to insert or there is no longer any element in the queue.
3. Do the same for all the successive elements.
4.After you have inserted K elements remove from the bottom of the queue and that will be the largest element in the queue. Do this for every window of size K i.e. after you have found the biggest element in the window size of K initially, you should do the same whenever you are inserting a new element. Why? Because the new element addition will be the new window of size K.

- aka January 24, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 vote

Here is a method that is guaranteed to get O(n(logk)) which is attractive is k is large and close to n. In this method, we use a Dictionary<int,int> to store the counts of numbers in the K window and a SortedSet<int> (available in .Net) storing the sorted different elements seen in the window. The sortedSet implements a red-black tree, allowing sorted inserts and remove of log n (n => k in this case).

For each movement of the window, we remove the last element of the window from the Dictionary, if this deletes the count of that element goes to 0, we remove it from the sortedSet as well, in O(logk) time. Then we Add an element into the dictionary, if it exists, we increment the count, if it doesn't we insert it in the dictionary, and Add it to the sortedSet, again in O(logk) time.

I implemented a class KWindow that contains my SortedSet and my dictionary to abstract Adding and Removing.

public static void PrintMax(int[] A, int k)
        {
            KWindow kWindow = new KWindow();
            // fill first window and print
            for (int i = 0; i < Math.Min(k,A.Length); i++)
            {
                kWindow.Add(A[i]);
                Console.WriteLine(kWindow.listInts.Max);
            }

            for (int i = 1; i <=A.Length- k; i++)
            {
                kWindow.Add(A[i+k-1]);
                kWindow.Remove(A[i - 1]);
                Console.WriteLine(kWindow.listInts.Max);
            }
        }

        public class KWindow
        {
            public SortedSet<int> listInts;
            public Dictionary<int, int> numCounts;

            public KWindow()
            {
                listInts = new SortedSet<int>();
                numCounts = new Dictionary<int, int>();
            }

            public void Add(int value)
            {
                if (numCounts.ContainsKey(value))
                {
                    numCounts[value]++;
                }
                else
                {
                    numCounts.Add(value, 1);
                    listInts.Add(value);
                }
            }

            public void Remove(int value)
            {
                if (numCounts.ContainsKey(value))
                {// don't really need to check in this application but to be reusable
                    if (numCounts[value] == 1)
                    {
                        numCounts.Remove(value);
                        listInts.Remove(value);
                    }
                    else
                    {
                        numCounts[value]--;
                    }
                }
            }
        }

Running this on

int [] Arr = new int[] {1,2,2,4,5,45,6,7,4,3,4,5,6,3,9};
            PrintMax(Arr,4);

gives
1,2,2,4,5,45,45,45,45,7,7,5,6,6,9

- djtrance January 31, 2016 | Flag Reply
Comment hidden because of low score. Click to expand.
0
of 0 vote

it is a O(n) algorithm.

maxv = sum(A[1] to A[k])
for i = k+1 to n:
tp = maxv - A[i-k] + A[i]
if tp > maxv:
maxv = tp

print maxv

- runwithfullspeed February 02, 2016 | Flag Reply


Add a Comment
Name:

Writing Code? Surround your code with {{{ and }}} to preserve whitespace.

Books

is a comprehensive book on getting a job at a top tech company, while focuses on dev interviews and does this for PMs.

Learn More

Videos

CareerCup's interview videos give you a real-life look at technical interviews. In these unscripted videos, watch how other candidates handle tough questions and how the interviewer thinks about their performance.

Learn More

Resume Review

Most engineers make critical mistakes on their resumes -- we can fix your resume with our custom resume review service. And, we use fellow engineers as our resume reviewers, so you can be sure that we "get" what you're saying.

Learn More

Mock Interviews

Our Mock Interviews will be conducted "in character" just like a real interview, and can focus on whatever topics you want. All our interviewers have worked for Microsoft, Google or Amazon, you know you'll get a true-to-life experience.

Learn More