算法题:最大的连续子数组选择
Algorithms question: Largest contiguous subarray selection
问。给定两个长度相等的数组 A 和 B,找到索引 [i,j] 的最大可能连续子数组,使得 max(A[i: j]) < min(B[i: j]).
示例:A = [10, 21, 5, 1, 3], B = [3, 1, 4, 23, 56]
解释:A[4] = 1, A[5] = 3, B[4] = 23, B[5] = 56, max(A[4], A[5]) < min( B[4], B[5])
索引为[4,5](含),最大连续子数组长度为2
我可以用 O(n2) 的蛮力方法做到这一点,但似乎无法降低时间复杂度。有什么想法吗?
这可以在O(n log(n))
中解决。
这是我的技术概述。
我的想法看起来像 A 中最大的“上升水位”,跟踪 A
中出现的“岛屿”,以及 B
中淹没的“岛屿”。并记录从 A
出现后或从 B
下沉消失前的最大交点。
我们将需要 A
和 B
区间的 2 个平衡二叉树,以及一个事件优先级队列。
间隔树需要支持对数“查找并包含 i
的 return 间隔(如果存在)”。它还需要支持对数“将 i
添加到树,extending/merging 间隔适当,以及 return 新间隔”。同样,对数“从树中删除 i
,根据需要缩短、拆分或删除其间隔”。
事件的形式可以是“删除 B[i]
”或“添加 A[i]
”。优先级队列首先按元素的大小added/subtracted排序,然后将B
放在A
之前。 (因此,在从 B
中删除所有大小为 x
的元素之前,我们不会将大小为 x
的元素更改为 A
。)
考虑到这一点,这里是伪代码如何做到这一点。
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)
处理任何事件是O(log(n)
。有 2n = O(n)
个事件。所以总时间是 O(n log(n))
.
根据问题来看,有2个条件:
- max(A[i,j-1]) < min(B[i,j-1])
- max(A[i,j]) >= min(B[i,j])
- 假设maxA是[i,j]中A数组中最大项的索引,minB是[i,j]中B数组中最小项的索引;锚是 min(maxA, minB)
那么我们将有:max(A[i+k,anchor]) >= min(B[i+k,anchor]) ∀ k in [i+1,anchor].
所以我想出了一个简单的算法,如下所示:
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;
}
使用这种方法,复杂度为 O(n)。
如果需要,我们可以更改输出以获取其他信息。
O(n)解:
从左向右移动索引j
并将i
拖到后面,这样window从i
到j
有效。因此,总是将 j
增加 1,然后根据需要增加 i
以使 window 有效。
为此,保留一个队列 Aq
非递增 A 值的索引。然后 A[Aq[0]]
告诉你 window 中的最大 A 值。同样,为非递减 B 值保留一个队列。
对于每一个新的j
,首先根据新的A值和新的B值更新Aq
和Bq
。然后,当 window 无效时,增加 i
并删除 Aq[0]
和 Bq[0]
(如果它们是 i
)。当 j
和 i
都更新时,用 window 大小更新结果 j - i - 1
.
Python 实施:
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
与原始暴力参考解决方案进行比较的测试结果:
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
测试代码(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}')
问。给定两个长度相等的数组 A 和 B,找到索引 [i,j] 的最大可能连续子数组,使得 max(A[i: j]) < min(B[i: j]).
示例:A = [10, 21, 5, 1, 3], B = [3, 1, 4, 23, 56]
解释:A[4] = 1, A[5] = 3, B[4] = 23, B[5] = 56, max(A[4], A[5]) < min( B[4], B[5])
索引为[4,5](含),最大连续子数组长度为2
我可以用 O(n2) 的蛮力方法做到这一点,但似乎无法降低时间复杂度。有什么想法吗?
这可以在O(n log(n))
中解决。
这是我的技术概述。
我的想法看起来像 A 中最大的“上升水位”,跟踪 A
中出现的“岛屿”,以及 B
中淹没的“岛屿”。并记录从 A
出现后或从 B
下沉消失前的最大交点。
我们将需要 A
和 B
区间的 2 个平衡二叉树,以及一个事件优先级队列。
间隔树需要支持对数“查找并包含 i
的 return 间隔(如果存在)”。它还需要支持对数“将 i
添加到树,extending/merging 间隔适当,以及 return 新间隔”。同样,对数“从树中删除 i
,根据需要缩短、拆分或删除其间隔”。
事件的形式可以是“删除 B[i]
”或“添加 A[i]
”。优先级队列首先按元素的大小added/subtracted排序,然后将B
放在A
之前。 (因此,在从 B
中删除所有大小为 x
的元素之前,我们不会将大小为 x
的元素更改为 A
。)
考虑到这一点,这里是伪代码如何做到这一点。
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)
处理任何事件是O(log(n)
。有 2n = O(n)
个事件。所以总时间是 O(n log(n))
.
根据问题来看,有2个条件:
- max(A[i,j-1]) < min(B[i,j-1])
- max(A[i,j]) >= min(B[i,j])
- 假设maxA是[i,j]中A数组中最大项的索引,minB是[i,j]中B数组中最小项的索引;锚是 min(maxA, minB)
那么我们将有:max(A[i+k,anchor]) >= min(B[i+k,anchor]) ∀ k in [i+1,anchor].
所以我想出了一个简单的算法,如下所示:
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;
}
使用这种方法,复杂度为 O(n)。 如果需要,我们可以更改输出以获取其他信息。
O(n)解:
从左向右移动索引j
并将i
拖到后面,这样window从i
到j
有效。因此,总是将 j
增加 1,然后根据需要增加 i
以使 window 有效。
为此,保留一个队列 Aq
非递增 A 值的索引。然后 A[Aq[0]]
告诉你 window 中的最大 A 值。同样,为非递减 B 值保留一个队列。
对于每一个新的j
,首先根据新的A值和新的B值更新Aq
和Bq
。然后,当 window 无效时,增加 i
并删除 Aq[0]
和 Bq[0]
(如果它们是 i
)。当 j
和 i
都更新时,用 window 大小更新结果 j - i - 1
.
Python 实施:
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
与原始暴力参考解决方案进行比较的测试结果:
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
测试代码(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}')