# [53] 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入:[-2,1,-3,4,-1,2,1,-5,4]\
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
这道题根据leetcode
给的tag
提示,我们有两种解题思路。
# 动态规划解法
一种是利用动态规划的思想。
这道题得分两步考虑。想要求出在 0 -> n 点的最大子序列和,那么就要看 0 -> n-1 点的和是否为正数,如果为负数的话,那还不如就只取 n 点一个数作为子序列来得大了。
因此,我们可以得出 0-n 最大和的状态转移方程:Sn = MAX(nums[n], Sn-1 + nums[n])
第二步,我们需要选出整个数组中任意连续子序列的最大和。那么很简单,将从 0->1......0->n 中所有的最大和都记录下来,选择最大值即可。
由于本题不需要回溯子序列索引,因此不需要记录每一步的最大值。
function maxSubArray(nums: number[]): number {
const len = nums.length;
const dp = Array(len).fill(Number.MIN_SAFE_INTEGER);
dp[0] = nums[0];
let res = nums[0];
// 状态转移方程:Sn = Math.max(nums[n], Sn-1 + nums[n])
for (let i = 1; i < len; i++) {
dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
res = Math.max(res, dp[i]);
}
return res;
}
将代码稍微改动一下,就能输出最大子序列了。
function maxSubArray(nums: number[]): number {
const len = nums.length;
const dp = Array(len).fill(Number.MIN_SAFE_INTEGER);
dp[0] = nums[0];
let arr: number[] = [nums[0]];
let resArr: number[] = [nums[0]];
let res = nums[0];
// 状态转移方程:Sn = Math.max(nums[n], Sn-1 + nums[n])
for (let i = 1; i < len; i++) {
if (dp[i - 1] < 0) {
dp[i] = nums[i];
arr = [nums[i]];
} else {
dp[i] = dp[i - 1] + nums[i];
arr.push(nums[i]);
}
if (dp[i] > res) {
// 此时更新子序列
res = dp[i];
resArr = [...arr];
}
}
console.log(resArr);
return res;
}
观察 dp 数组可知,dp[i] 仅与 dp[i-1] 相关,因此完全可以用一个变量替代 dp 数组,从而节省空间复杂度。
function maxSubArray(nums: number[]): number {
let dp = nums[0];
let res = nums[0];
for (let i = 1; i < nums.length; i++) {
dp = Math.max(nums[i], dp + nums[i]);
res = Math.max(res, dp);
}
return res;
}
# 分治法
最大子序列和的位置有以下三种情况:
- 考虑中间元素 nums[m], 跨越左右两部分,这里从中间元素开始,往左求出后缀最大,往右求出前缀最大,保持连续性。
- 不考虑中间元素,最大子序列和出现在左半部分,递归求解左边部分最大子序列和
- 不考虑中间元素,最大子序列和出现在右半部分,递归求解右边部分最大子序列和 分别求出三种情况下最大子序列和,三者中最大值即为最大子序列和。
var maxSubArray = function(nums) {
return SubHelper(nums, 0, nums.length - 1);
};
function SubHelper(nums, l, r) {
if (l === r) return nums[l];
let sum = 0;
let leftMax = Number.MIN_SAFE_INTEGER;
let rightMax = Number.MIN_SAFE_INTEGER;
const mid = l + ((r - l) >> 1);
const subLeft = SubHelper(nums, l, mid);
const subRight = SubHelper(nums, mid + 1, r);
for (let i = mid; i >= l; i--) {
sum += nums[i];
if (sum > leftMax) leftMax = sum;
}
sum = 0;
for (let i = mid + 1; i <= r; i++) {
sum += nums[i];
if (sum > rightMax) rightMax = sum;
}
return Math.max(leftMax + rightMax, subLeft, subRight);
}
← [44] 通配符匹配 [62] 不同路径 →