The problem is this, given an array of length say N
, we have to find all subsequences of length W
such that those W
elements, when sorted, forms an arithmetic progression with interval 1. So for an array like [1,4,6,3,5,2,7,9]
, and W
as 5, the slice [4,6,3,5,2]
can be regarded as one such subsequence, since, when sorted, it yields [2,3,4,5,6]
, an A.P with common difference 1.
The immediate solution which comes to the mind is to have a sliding window, for each new element, pop the old one, push the new one, sort the window, and if for that window, window[w-1] - window[0] + 1 = w
, then it is such a subsequence. However, it takes O(NlogN)
time, whereas the solution at Codechef proposes a O(N)
time algorithm that uses double-ended queue. I am having difficulty in understanding the algorithm, what is being pushed and popped, and why so, and how it maintains the window in sorted order without the need to resort with each new element. Can anybody explain it?
You are correct in observing that a segment is valid if
max(segment) - min(segment) + 1 = W
. So, the problem reduces to finding the min and max of all lengthW
segments inO(N)
.For this, we can use a deque
D
. Suppose we want to find the min. We will store the indexes of elements inD
, assuming 0-based indexing. LetA
be the original array.For each
i
, this will give you the minimum in[i - W + 1, i]
as the element at indexD.first()
.popFirst()
removes the first element fromD
. We have to do this when the first element inD
is more thanW
steps away fromi
, because it will not contribute to the minimum in the interval above.popLast()
removes the last element fromD
. We do this to maintain the sorted order: if the last element inD
is the index of an element larger thanA[i]
, then addingi
at the end ofD
would break the order. So we have to keep removing the last element to ensure thatD
stays sorted.pushBack()
adds an element at the end ofD
. After adding it,D
will definitely remain sorted.This is
O(1)
(to find a min, the above pseudocode isO(n)
) because each element will be pushed and popped to / fromD
at most once.This works because
D
will always be a sliding window of indexes sorted by their associated value inA
. When we are at an element that would break this order, we can pop elements fromD
(the sliding window) until the order is restored. Since the new element is smaller than those we are popping, there is no way those can contribute to a solution.Note that you can implement this even without the methods I used by keeping two pointers associated with
D
:start
andend
. Then makeD
an array of lengthN
and you are done.