这也可以用O(n)的时间复杂度来完成:
首先,创建一个辅助数组,用于计算数组的每个前缀和:
sums[i] = arr[0] + arr[1] + ... + arr[i]
以上可以在O(n)
时间内计算出来。
接下来,创建一个哈希映射Map<Integer,List<Integer>>
,其中键表示前缀和,值是具有此前缀和的索引列表。伪代码:
Map<Integer,List<Integer>> map = new HashMap<>();
for (int i = 0; i < sums.length; i++) {
List<Integer> l = map.get(sums[i]);
if (l == null) {
l = new ArrayList<>();
map.put(sums[i],l);
}
l.add(i);
}
现在,遍历sums数组,对于每个元素x,检查map是否包含一个键k,使得x-k==S。
这可以通过检查是否有一个键k=S-x来完成,在哈希映射中为O(1)。
如果存在这样的键,则获取值列表中的最后一个索引,这也可以在O(1)时间内完成,并将其作为候选匹配项。
伪代码:
int currentMaxLength = Integer.MIN_VALUE;
for (int i = 0; i < sums.length; i++) {
int k = S-sums[i];
List<Integer> l = map.get(k);
if (l == null) continue;
int width = Math.abs(l.getLast() - i);
if (width > currentMaxLength) currentMaxLength = width;
}
return currentMaxLength;
这个想法是,如果你有多个匹配项对于一些
x1, x2
,这样的
x2-x1 = S
,其中
x1, x2
是前缀和,则最长子数组的候选者是第一个出现
x1
的位置,以及最后出现
x2
的位置。
对于
x1
,它是主循环中
所引用的元素,并且它始终被视为候选项。
对于
x2
,您将始终检查
x2
的最后出现位置,因此这是正确的。
快速提示:实际代码还需要考虑sums[-1] = 0
。
Java代码:
public static int solution(int[] arr, int S) {
int[] sums = new int[arr.length+1];
int sum = 0;
sums[0] = 0;
for (int i = 0; i < arr.length; i++) sums[i+1] = sum = sum+arr[i];
Map<Integer,List<Integer>> map = new HashMap<>();
for (int i = 0; i < sums.length; i++) {
List<Integer> l = map.get(sums[i]);
if (l == null) {
l = new ArrayList<>();
map.put(sums[i],l);
}
l.add(i);
}
int currentMaxLength = Integer.MIN_VALUE;
for (int i = 0; i < sums.length; i++) {
int k = S - sums[i];
List<Integer> l = map.get(k);
if (l == null) continue;
int width = Math.abs(l.get(l.size()-1) - i);
if (width > currentMaxLength) currentMaxLength = width;
}
return currentMaxLength;
}
public static void main(String[] args) {
System.out.println(solution(new int[] { 1, 0, -1, 1, 1, -1, -1 }, 2));
}
O(n)
,最坏情况下确实为O(n^2)
。请注意,如果数组包含例如小值,则可以使用位集代替,以换取O(S)
的内存(其中 S 是最大前缀和与最小前缀和之间的差)。另一种选择是使用TreeMap
,这将使算法在所有情况下都以O(nlogn)
运行。 - amit