Reputation: 543
Given an array A of size n we need to find max of j-i such that for all k, i < k < j, a[i] <= a[k] and a[k] <= a[j].
I have been able to solve it in O(n^2) by dynamic programming. Is there a better solution?
Upvotes: 2
Views: 150
Reputation: 65427
Here’s a new, tested, linear-time solution.
The code below computes the length of the longest subarray of each
prefix a[:j + 1]
for j
from 0
to n - 1
(stored in k
). The
stack starts
holds the indexes s
from 0
to j
such that
a[s] <= a[t]
for all t
from s + 1
to j
, i.e., those indexes that
can start a valid subarray ending at index j
or after. The stack
blockers
holds the indexes s
from 0
to j
such that a[s] > a[t]
for all t
from s + 1
to j
, i.e., the minimal set of elements
needed to rule out all invalid subarrays by dint of the last element not
being maximum. Maintenance of starts
and blockers
takes amortized
constant time, since at most one element is appended per loop iteration.
The valid subarrays ending at index j
are those that start at some
index in starts
greater than that of all current blockers except j
itself. Naively, we could scan backward each time to find the least
suitable starting index, but then we might scan too much on average.
Enter the variable i
, which holds the index into starts
most
recently scanned. By starting the next scan at i + 1
, we ensure that
the amortized cost of the scan is constant, while still scanning all
subarrays that may be longer than k
.
import itertools
def longest(a):
k = 0
n = len(a)
starts = []
blockers = []
for j in range(n):
while starts and a[starts[-1]] > a[j]:
del starts[-1]
starts.append(j)
while blockers and a[blockers[-1]] <= a[j]:
del blockers[-1]
if blockers:
i = min(i + 1, len(starts) - 1)
while i > 0 and starts[i - 1] > blockers[-1]:
i -= 1
else:
i = 0
k = max(k, j + 1 - starts[i])
blockers.append(j)
return k
def longest_slow(a):
n = len(a)
for k in range(n, 0, -1):
for i in range(n - k + 1):
b = a[i:i + k]
if b[0] == min(b) and max(b) == b[-1]:
return k
return 0
def test():
for n in range(9):
for a in itertools.permutations(range(n)):
assert longest(a) == longest_slow(a)
test()
Upvotes: 3
Reputation: 496
For each number, lets call it X. Find the last number thats larger than X out of those you have processed. For the sequence [3, 7, 5, 2, 1, 3, 2, 4], when you are processing X = 4, the last number thats largest is 5 and position Y = 2 (0-indexed).
Y can be found in O(log N) by maintaining a segment tree/fenwick tree that answers range maximum queries with the index of the tree being the values in the sequence and the values of the tree being the index in the sequence. Eg: when adding value X to the segment tree, we update index X of the segment tree with the index of X. To find Y, we simply query the tree for the range maximum where index > X.
Now we need to find the index of the minimum number between index Y and the index of X. This can be done using another segment tree/sparse table that handles range minimum query on the original sequence. If there are multiple minimum numbers, compute the one with the lowest index. Lets call the index Z. This will also require O(log N).
The answer should be the maximum of (index of X)-Z by doing these processing on every number in the sequence, yielding an overall complexity of O(N log N).
For the case [-1000,1000,0,1,2,3,4] provided by nikhil_vyas, When processing the last number, X = 4, Y will be 1 (value 1000), Z should be in between index 1 and 6 and it is index 2. Hence the answer would be (index of 4)-2 = 6-2 = 4. (i = 2, j = 6)
Edit: Y will be default to 'index -1' if there is no number larger than X so far. In that case, Z can exist between index 0 up till the current processed number. If Z does not exist/cannot exist, ignore this number kf the sequence and move on to the next.
Upvotes: 2