Q. Given two arrays, A and B, of equal length, find the largest possible contiguous subarray of indices [i,j] such that max(A[i: j]) < min(B[i: j]).
Example: A = [10, 21, 5, 1, 3], B = [3, 1, 4, 23, 56]
Explanation: A[4] = 1, A[5] = 3, B[4] = 23, B[5] = 56, max(A[4], A[5]) < min(B[4], B[5])
The indices are [4,5] (inclusive), and the largest contiguous subarray has length 2
I can do this in O(n2) brute force method but cannot seem to reduce the time complexity. Any ideas?
CodePudding user response:
O(n) solution:
Move index j
from left to right and drag i
behind so that the window from i
to j
is valid. So always increase j
by 1, and then increase i
as much as needed for the window to be valid.
To do that, keep a queue Aq
of indexes of non-increasing A-values. Then A[Aq[0]]
tells you the max A-value in the window. Similarly, keep a queue for non-decreasing B-values.
For each new j
, first update Aq
and Bq
according to the new A-value and new B-value. Then, while the window is invalid, increase i
and drop Aq[0]
and Bq[0]
if they're i
. When both j
and i
are updated, update the result with the window size j - i - 1
.
Python implementation:
def solution(A, B):
Aq = deque()
Bq = deque()
i = 0
maxlen = 0
for j in range(len(A)):
while Aq and A[Aq[-1]] < A[j]:
Aq.pop()
Aq.append(j)
while Bq and B[Bq[-1]] > B[j]:
Bq.pop()
Bq.append(j)
while Aq and A[Aq[0]] >= B[Bq[0]]:
if Aq[0] == i:
Aq.popleft()
if Bq[0] == i:
Bq.popleft()
i = 1
maxlen = max(maxlen, j - i 1)
return maxlen
Test results from comparing against a naive brute force reference solution:
expect: 83 result: 83 same: True
expect: 147 result: 147 same: True
expect: 105 result: 105 same: True
expect: 71 result: 71 same: True
expect: 110 result: 110 same: True
expect: 56 result: 56 same: True
expect: 140 result: 140 same: True
expect: 109 result: 109 same: True
expect: 86 result: 86 same: True
expect: 166 result: 166 same: True
Testing code (Try it online!)
from timeit import timeit
from random import choices
from collections import deque
from itertools import combinations
def solution(A, B):
Aq = deque()
Bq = deque()
i = 0
maxlen = 0
for j in range(len(A)):
while Aq and A[Aq[-1]] < A[j]:
Aq.pop()
Aq.append(j)
while Bq and B[Bq[-1]] > B[j]:
Bq.pop()
Bq.append(j)
while Aq and A[Aq[0]] >= B[Bq[0]]:
if Aq[0] == i:
Aq.popleft()
if Bq[0] == i:
Bq.popleft()
i = 1
maxlen = max(maxlen, j - i 1)
return maxlen
def naive(A, B):
return max((j - i 1
for i, j in combinations(range(len(A)), 2)
if max(A[i:j 1]) < min(B[i:j 1])),
default=0)
for _ in range(10):
n = 500
A = choices(range(42), k=n)
B = choices(range(1234), k=n)
expect = naive(A, B)
result = solution(A, B)
print(f'expect: {expect:3} result: {result:3} same: {result == expect}')
CodePudding user response:
I can see based on the problem, saying we have 2 conditions:
- max(A[i,j-1]) < min(B[i,j-1])
- max(A[i,j]) >= min(B[i,j])
- saying maxA is index of max item in A array in [i,j], minB is index of min item in B array in [i,j]; and anchor is min(maxA, minB)
Then we will have: max(A[i k,anchor]) >= min(B[i k,anchor]) ∀ k in [i 1,anchor].
So I come up with a simple algorithm like below:
int extractLongestRange(int n, int[] A, int[] B) {
// n is size of both array
int size = 0;
for(int i = 0; i < n; i ){
int maxAIndex = i;
int minBIndex = i;
for(int j = i; j < n; j ){
if(A[maxAIndex] < A[j]){
maxAIndex = j;
}
if(B[minBIndex] > B[j]){
minBIndex = j;
}
if(A[maxAIndex] >= B[minBIndex]){
if(size < j - i){
size = j - i;
}
// here, need to jump back to min of maxAIndex and minBIndex.
i = Math.min(maxAIndex, minBIndex);
break;
}
// this case, if j reach the end of array
if(j == n - 1){
if(size < j - i 1){
size = j - i 1;
i = j;
}
}
}
}
return size;
}
With this approach, the complexity is O(n). We can change the output to pick-up the other information if needed.
CodePudding user response:
This can be solved in O(n log(n))
.
Here is an outline of my technique.
My idea looks like a "rising water level" of maximum in A, keeping track of the "islands" emerging in A
, and the "islands" submerging in B
. And recording the maximum intersections after they appear from A
emerging, or before they disappear from B
sinking.
We will need 2 balanced binary trees of intervals in A
and B
, and a priority queue of events.
The tree of intervals need to support a logarithmic "find and return interval containing i
if it exists". It also needs to support a logarithmic "add i
to tree, extending/merging intervals as appropriate, and return new interval". And likewise a logarithmic "remove i
from tree, shortening, splitting, or deleting its interval as appropriate".
Events can be of the form "remove B[i]
" or "add A[i]
". The priority queue is sorted first by the size of the element added/subtracted, and then by putting B
before A
. (So we do not elements of size x
to A
until all of the elements of size x
are removed from B
.)
With that in mind, here is pseudocode for how to do it.
Put all possible events in the priority queue
Initialize an empty tree of intervals A_tree
Initialize a tree of intervals B_tree containing the entire range
Initialize max interval to (0, -1) (length 0)
For each event (type, i) in queue:
if event.type = A:
new_interval = A_tree.add(event.i)
if event.i in B_tree:
Intersect event.i's A_tree interval with event.i's B_tree interval
if intersection is longer than max_interval:
update to new and better max_interval
else:
if event.i in A_tree:
Intersect event.i's A_tree interval with event.i's B_tree interval
if intersection is longer than max_interval:
update to new and better max_interval
B_tree.remove(event.i)
Processing any event is O(log(n)
. There are 2n = O(n)
events. So overall time is O(n log(n))
.