求解整数数组的连续子数组的最大和算法学习笔记

给定一个整数数组,找出总和最大的连续数列,并返回总和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

 

一、暴力求解

暴力解法是最容易想到的。对于连续的子序列,我们需要枚举子序列的上界(i)和下界(j),然后对子序列求和,并设一个全局变量maxSum存放最大子序和,每一轮枚举求和sum和maxSum比较,

maxSum取较大的,这样最终能取到全局的最大maxSum。

时间复杂度:枚举子序列上界和下界需要两重循环,计算量为:\( \sum_{i=1}^{n} i = (n^2+n)/2\),复杂度为\(O(n^2)\)

 

1 function maxSub1(nums) {
2     let maxSum = nums[0];
3     for (let i = 0, len = nums.length; i < len; i++) {
4       for (let j = i; j < len; j++) {
5         maxSum = Math.max(maxSum, sum(nums, i, j));
6       }
7     }
8     return maxSum;
9 }
1 function sum(nums, i, j) {
2     let sum  = 0;
3     for (let n = i; n <= j; n++) {
4         sum += nums[n];
5     }
6     return sum;  
7 }

 

二、动态规划求解

dp[i]:以nums[i]结尾的连续数列最大和;
dp[i]是否加入数列取决于前面的数列和是否大于0,若前面的数列小于0,则dp[i]从i开始一个新的数列。

dp[i] = Math.Max(dp[i-1] + dp[i], dp[i]);

由于我们求最大子序和maxSum,在计算每个dp[i]时我们都会更新maxSum,在计算dp[i]和更新maxSum时不需要知道dp[i-1](不包括dp[i-1])之前的结果,因为dp[i]只与dp[i-1]相关

(这里是不是也体现了动态规划的无后效性特征),所以可以用滚动数组思想简化代码,定义sum变量记录dp[i]的结果,每次重新计算dp[i]时sum存的就是dp[i-1]的结果。

时间复杂度:\(O(n)\)

function maxSub2(nums) {
    let len = nums.length, maxSum = nums[0], sum = nums[0];
    for (let i = 1; i < len; i++) {
        sum = Math.max(sum + nums[i], nums[i]);
        maxSum = Math.max(maxSum, sum);
    }
    return maxSum;
}

 

三、分治法

把大数组分割成2个子数组,分别计算2个子数组的最大子序和leftSum、rightSum,还有横跨左右2个子数组的子数组的最大子序和midSum,返回三者中最大的Math.max(leftSum, rightSum, midSum),不断递归重复,一层层计算,一层层返回结果,最后组合成最终的解。问题规模在递归的过程中不断被减小,这是分治法的一个特点,难点是要把各种分类情况和边界条件考虑周全。

记录下midSum计算时要注意的:

首先m是分割数组的枢轴,它不包含在左子数组或右子数组中, 它属于跨左右子数组情况。

从m-1开始向左搜索得到 lMidSum,从m+1开始向右搜索得到 rMidSum,从而得到跨左右子数组最大子序和midSum = max(lMidSum + nums[m], nums[m], nums[m] + rMidSum, lMidSum+ nums[m] + rMidSum)

 1 function maxSub(nums) {
 2     let n = nums.length;
 3     return maxSum(0, n -1, nums); 
 4 }
 5 
 6 function maxSum(l, r, nums) {
 7    if (l === r) return nums[l];
 8    let m = l + ((r - l) >> 1);
 9  //  console.log('m = '+m);
10    let leftSum = maxSum(l, m - 1 < l ? l : m - 1, nums);
11    let rightSum = maxSum(m + 1 > r ? r: m + 1, r, nums);
12    let lMidSum = 0, rMidSum = 0, sum = 0, i = m - 1;
13    while (i >= l) {
14        sum += nums[i--];
15        lMidSum = Math.max(lMidSum, sum);
16    }
17    i = m + 1;
18    sum = 0;
19    while (i <= r) {
20        sum += nums[i++];
21        rMidSum = Math.max(rMidSum, sum);
22    }
23    let midSum = nums[m];
24    midSum = Math.max(lMidSum + midSum, midSum, midSum + rMidSum, lMidSum + midSum + rMidSum);
25    return Math.max(leftSum, midSum, rightSum);
26 }

时间复杂度:\(O(NlogN)\)

 

posted on 2020-10-11 10:39  DavidXu2014  阅读(679)  评论(0编辑  收藏  举报