14

We are given an array of n elements and an integer k. Suppose that we want to slide a window of length k across the array, reporting the largest value contained in each window. For example, given the array

15  10   9  16  20  14  13

Given a window of length 4, we would output

[15  10   9  16] 20  14  13   ---> Output 16
 15 [10   9  16  20] 14  13   ---> Output 20
 15  10 [ 9  16  20  14]  13  ---> Output 20
 15  10   9 [16  20  14  13]  ---> Output 20

So the result would be

16  20  20  20

I was approaching the problem by keeping track of the maximum element of the window at each point, but ran into a problem when the largest element gets slid out of the window. At that point, I couldn't think of a fast way to figure out what the largest remaining element is.

Does anyone know of an efficient algorithm for solving this problem?

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
arvind.mohan
  • 894
  • 2
  • 11
  • 23
  • Now the Question is, What are your ideas about it? – Alok Save Aug 15 '11 at 14:13
  • 1
    @Als: I was thinking about it whole evening, I was just approaching like a window of length of subarray that is moving and we need to find the max out of it. It was working really good. Then I stuck in a problem where current maximum element goes out of "sliding window". At that point I needed to store all the current elements of sliding window. :( – arvind.mohan Aug 15 '11 at 14:18
  • 1
    mohan: Feel free to voice your thoughts in the Q's it tells readers, what you have tried, otherwise they just feel you did nothing than asking the Q here. – Alok Save Aug 15 '11 at 14:21
  • 1
    Hint: Dynamic Programming (http://en.wikipedia.org/wiki/Dynamic_programming) I think would let you solve the problem in O(M*N) where M is the length and N the number of elements in the array. – Matthieu M. Aug 15 '11 at 14:21
  • 1
    @Matthieu: Isn't *O(MN)* the complexity of the "naive" algorithm, i.e. a simple 2D loop? – Oliver Charlesworth Aug 15 '11 at 14:29
  • @Oli: yep exactly...a brute force approach will give O(M*N) time complexity. – arvind.mohan Aug 15 '11 at 14:31
  • @Oli: it is, to output one array. The Dynamic Programming approach allows memoization. Not sure if it's necessary reading the question again :) – Matthieu M. Aug 15 '11 at 14:33
  • ttp://wcipeg.com/wiki/Range_minimum_query gives several algorithms. – misterbee Jan 19 '14 at 20:36

6 Answers6

14

This older question discusses how to build a queue data structure supporting insert, dequeue, and find-min all in O(1) time. Note that this is not a standard priority queue, but instead is a queue in which at any point you can find the value of the smallest element it contains in O(1) time. You could easily modify this structure to support find-max in O(1) instead of find-min, since that's more relevant to this particular problem.

Using this structure, you can solve this problem in O(n) time as follows:

  1. Enqueue the first k elements of the array into the special queue.
  2. For each element in the rest of the array:
    1. Use the queue's find-max operation to report the largest element of the current subrange.
    2. Dequeue an element from the queue, leaving the last k-1 elements of the old range in place.
    3. Enqueue the next element from the sequence, causing the queue to now hold the next k-element subrange of the sequence.

This takes a total of O(n) time, since you visit each array element once, enqueuing and dequeuing each at most once, and calling find-max exactly n-k times. I think this is pretty cool, since the complexity is independent of k, which doesn't initially seem like it necessarily should be possible.

Hope this helps! And thanks for asking a cool question!

Community
  • 1
  • 1
templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • This does not work. I checked it, and the suggested approach is just wrong. The linked solution for a) the stack with min property and b) the queue from 2 stacks each for itself work, but both combined do NOT work. Dont know why nobody realized it, but it is just wrong. The problem is that both stacks can for each hold the right min value, but it is not possible to keep it consistent over both stacks. Just try it out for this example: Push 4 Values with min/max. Pop one, all 4 get copied, and the result is ok. Now push once more and pop again, the new pushed value is not considered. – flolo Aug 15 '11 at 20:48
  • @flolo- Can you elaborate on this? I'm pretty sure that the answer does indeed work as long as you look at the minimum elements of the two stacks. What specifically went wrong? Also, I posted an answer to that question that also implements the min-queue backed by a different structure (a Cartesian tree), so even if the first answer is wrong I believe that what I've posted still correctly implements the abstraction. I'll investigate the first answer right now. – templatetypedef Aug 15 '11 at 20:53
  • @flolo- Also, while I understand why you downvoted this, can you please remove the downvote? You still haven't ruled out the alternative implementation of the min-queue that I proposed in the previous question, and from what it sounds like I think you may have misimplemented the two-stack solution (did you look at the min element out of both stacks?) – templatetypedef Aug 15 '11 at 20:54
  • @flolo- I just implemented the two-stack solution on my computer and it's working perfectly fine. The only tricky part is that you have to remember to look at the mins of the two stacks. Looking at just one in isolation doesn't work correctly, as you noted. – templatetypedef Aug 15 '11 at 21:03
  • OH, maybe you are right that combining the maximum of both stacks. I tried (the answer is not very explicit in this point) to copy the complete pairs from stack 1 to stack 2. If you create in stack 2 the max values again and always look at both stacks and max them it seems to work: Very nice solution. – flolo Aug 15 '11 at 21:15
1

You can achieve O(n) complexity by using Double-ended queue.

Here is C# implementation

    public static void printKMax(int[] arr, int n, int k)
    {
        Deque<int> qi = new Deque<int>();
        int i;
        for (i=0;i< k; i++) // first window of the array
        {
            while ((qi.Count > 0) && (arr[i] >= arr[qi.PeekBack()]))
            {
                qi.PopBack();
            }
            qi.PushBack(i);
        }

        for(i=k ;i< n; ++i)
        {
            Console.WriteLine(arr[qi.PeekFront()]); // the front item is the largest element in previous window.
            while (qi.Count > 0 && qi.PeekFront() <= i - k) // this is where the comparison is happening!
            {
                qi.PopFront(); //now it's out of its window k 
            }
            while(qi.Count>0 && arr[i]>=arr[qi.PeekBack()]) // repeat
            {
                qi.PopBack();
            }
            qi.PushBack(i);
        }

        Console.WriteLine(arr[qi.PeekFront()]);
    }
aerin
  • 20,607
  • 28
  • 102
  • 140
  • The code statement `while (qi.Count > 0 && qi.PeekFront() <= i - k)` is not correct. I believe it should be `while (qi.Count > 0 && qi.Count > k)` – vikas pachisia Jan 23 '18 at 06:51
  • @vikaspachisia You believe? what you are saying is completely wrong. -_- i-k because it's out of window. – aerin Jan 30 '19 at 15:28
1

You can keep a Binary Search Tree of the current elements, for example, save them as value-occurrence pairs. Other than that, you sliding window algorithm should be good enough.

This way, select maximum (the max element in the subsection) will cost O(logL) time, L being the length of the current subsection; add new would also be O(logL). TO delete the oldest one, just search the value and decrements the count by 1, if the count goes to 0 delete it.

So the total time will be O(NlogL), N being the length of the array.

zw324
  • 26,764
  • 16
  • 85
  • 118
  • But from a max-heap, you can only extract maximum value not the value that is oldest....I think you get what I want to say. – arvind.mohan Aug 15 '11 at 14:24
1

You could proceed like a tabu search :

Loop through the list and get the max of the 4 first ith element. Then on the next step just check if the i+1th element is superior to the max of the previous elements

  • if i+1>=previous max then new max = i+1 reinialise tabu
  • if i+1< previous max then if the previous max was found less than N step ago keep the previous (here is the tabu )
  • if i+1< preivous max and the previous max is tabu then take the new max of the 4 i+1th elements.

I'm not sure it's clear but tell me if you have any question. below is a code in python to test it.

l=[15,10,9,16,20,14,13,11,12]
N=4
res=[-1] #initialise res
tabu=1  #initialise tabu
for k in range(0,len(l)):
#if the previous element res[-1] is higher than l[k] and not tabu then keep it
#if the previous is tabu and higher than l[k] make a new search without it
#if the previous is smaller than l[k] take the new max =l[k]

    if l[k]<res[-1] and tabu<N:
        tabu+=1
        res.append(res[-1])
    elif l[k] < res[-1] and tabu == N:
         newMax=max(l[k-N+1:k+1])
         res.append(newMax)
         tabu=N-l[k-N+1:k+1].index(newMax) #the tabu is initialized depending on the position of the newmaximum
    elif l[k] >= res[-1]:
        tabu=1
        res.append(l[k])

print res[N:] #take of the N first element

Complexity:

I updated the code thx to flolo and the complexity. it's not anymore O(N) but O(M*N)

The worst case is when you need to recalculate a maximum at each step of the loop. i e a strictly decreasing list for example.

at each step of the loop you need to recalculate the max of M elements

then the overall complexity is O(M*N)

Ricky Bobby
  • 7,490
  • 7
  • 46
  • 63
  • 1
    This is still *O(MN)*, worst case. – Oliver Charlesworth Aug 15 '11 at 14:51
  • 1
    @Oli, not sure it's N*M, in the worst case you will have one max of M elements every M steps of the loop. So O(M*N/M) = O(N) and one test per loop so total is O(2*N). tell me if i'm wrong – Ricky Bobby Aug 15 '11 at 15:15
  • 1
    @Ricky Bobby: I think you program is not right. Try a decreasing list with your algorithm, e.g. input [13,12,11,10,9,8,7,6,5,4,3,2,1], the result is wrong. – flolo Aug 15 '11 at 17:00
1

The best I can come up with quickly is O(n log m). You can get that by dynamic programming.

In the first pass you find max for every element the maximum from the element itself and the next.

Now you have n maximums (window size = 2).

Now you can find on this array the maximum from every element and the overnext in this array (gives you for each element the maximum for the next 4, ie window size = 4).

Then you can do it again, and again (and every time the window size doubles).

As one clearly sees the window size grows exponentially.

Therefor the runtime is O(n log m). The implementation is a bit tricky, because you must consider the corner and special cases (esp. when the windows size should not be a power of two), but they didnt influence the asymptotic runtime.

flolo
  • 15,148
  • 4
  • 32
  • 57
0

Please review my code. According to me I think the Time Complexity for this algorithm is

O(l) + O(n)

    for (int i = 0; i< l;i++){
        oldHighest += arraylist[i];
    }
    int kr = FindMaxSumSubArray(arraylist, startIndex, lastIndex);



    public static int FindMaxSumSubArray(int[] arraylist, int startIndex, int lastIndex){

    int k = (startIndex + lastIndex)/2;
    k = k - startIndex;
    lastIndex = lastIndex  - startIndex;

    if(arraylist.length == 1){
        if(lcount<l){
            highestSum += arraylist[0];
            lcount++;
        }
        else if (lcount == l){
            if(highestSum >= oldHighest){
                oldHighest = highestSum;
                result = count - l + 1;
            }
            highestSum = 0;
            highestSum += arraylist[0];
            lcount = 1;
        }
        count++;
        return result;
    }

    FindMaxSumSubArray(Arrays.copyOfRange(arraylist, 0, k+1), 0, k);
    FindMaxSumSubArray(Arrays.copyOfRange(arraylist, k+1, lastIndex+1), k+1, lastIndex);

    return result;
 }

I don't understand if this is better off to do in recursion or just linearly?

technazi
  • 888
  • 4
  • 21
  • 42