求解整数数组的连续子数组的最大和算法学习笔记
给定一个整数数组,找出总和最大的连续数列,并返回总和。
示例:
输入: [-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)\)