返回课程

最大子数组

重要性: 2

输入是一个数字数组,例如 arr = [1, -2, 3, 4, -9, 6]

任务是:找到 arr 中具有最大项目总和的连续子数组。

编写函数 getMaxSubSum(arr),它将返回该总和。

例如

getMaxSubSum([-1, 2, 3, -9]) == 5 (the sum of highlighted items)
getMaxSubSum([2, -1, 2, 3, -9]) == 6
getMaxSubSum([-1, 2, 3, -9, 11]) == 11
getMaxSubSum([-2, -1, 1, 2]) == 3
getMaxSubSum([100, -9, 2, -3, 5]) == 100
getMaxSubSum([1, 2, 3]) == 6 (take all)

如果所有项目都是负数,则意味着我们不取任何项目(子数组为空),因此总和为零

getMaxSubSum([-1, -2, -3]) = 0

请尝试想出一个快速解决方案:O(n2) 甚至 O(n) 如果可以。

打开一个带有测试的沙箱。

慢速解决方案

我们可以计算所有可能的子总和。

最简单的方法是取每个元素并计算从该元素开始的所有子数组的总和。

例如,对于 [-1, 2, 3, -9, 11]

// Starting from -1:
-1
-1 + 2
-1 + 2 + 3
-1 + 2 + 3 + (-9)
-1 + 2 + 3 + (-9) + 11

// Starting from 2:
2
2 + 3
2 + 3 + (-9)
2 + 3 + (-9) + 11

// Starting from 3:
3
3 + (-9)
3 + (-9) + 11

// Starting from -9
-9
-9 + 11

// Starting from 11
11

代码实际上是一个嵌套循环:外部循环遍历数组元素,内部循环计算从当前元素开始的子总和。

function getMaxSubSum(arr) {
  let maxSum = 0; // if we take no elements, zero will be returned

  for (let i = 0; i < arr.length; i++) {
    let sumFixedStart = 0;
    for (let j = i; j < arr.length; j++) {
      sumFixedStart += arr[j];
      maxSum = Math.max(maxSum, sumFixedStart);
    }
  }

  return maxSum;
}

alert( getMaxSubSum([-1, 2, 3, -9]) ); // 5
alert( getMaxSubSum([-1, 2, 3, -9, 11]) ); // 11
alert( getMaxSubSum([-2, -1, 1, 2]) ); // 3
alert( getMaxSubSum([1, 2, 3]) ); // 6
alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100

该解决方案的时间复杂度为 O(n2)。换句话说,如果我们将数组大小增加 2 倍,算法将运行时间延长 4 倍。

对于大型数组(1000、10000 或更多项),此类算法会导致严重的迟缓。

快速解决方案

让我们遍历数组并将当前元素的偏总和保存在变量 s 中。如果 s 在某个时刻变为负数,则将 s=0。所有此类 s 的最大值将是答案。

如果描述过于模糊,请查看代码,它足够短

function getMaxSubSum(arr) {
  let maxSum = 0;
  let partialSum = 0;

  for (let item of arr) { // for each item of arr
    partialSum += item; // add it to partialSum
    maxSum = Math.max(maxSum, partialSum); // remember the maximum
    if (partialSum < 0) partialSum = 0; // zero if negative
  }

  return maxSum;
}

alert( getMaxSubSum([-1, 2, 3, -9]) ); // 5
alert( getMaxSubSum([-1, 2, 3, -9, 11]) ); // 11
alert( getMaxSubSum([-2, -1, 1, 2]) ); // 3
alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100
alert( getMaxSubSum([1, 2, 3]) ); // 6
alert( getMaxSubSum([-1, -2, -3]) ); // 0

该算法只需要遍历数组一次,因此时间复杂度为 O(n)。

您可以在此处找到有关该算法的更多详细信息:最大子数组问题。如果仍然不清楚为什么它有效,请在上面的示例中跟踪该算法,看看它是如何工作的,这比任何文字都更好。

在沙盒中打开带有测试的解决方案。