Home > Enterprise >  Algorithms question: Largest contiguous subarray selection
Algorithms question: Largest contiguous subarray selection

Time:09-17

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)).

  • Related