最大子序和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
- 1 <= nums.length <= 105
- -104 <= nums[i] <= 104
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。
解法一
使用分治算法求解最大子序和问题的思路是将数组分成两半,分别求解左半部分的最大子序和、右半部分的最大子序和,以及跨越中点的最大子序和。这三个值中的最大值即为整个数组的最大子序和。
ini
function maxSubArray(nums: number[]): number {
if (nums.length === 0) {
return 0;
}
return divideAndConquer(nums, 0, nums.length - 1);
}
function divideAndConquer(nums: number[], left: number, right: number): number {
if (left === right) {
return nums[left]; // 单个元素的最大子序和就是该元素本身
}
const mid = Math.floor((left + right) / 2);
// 分别求解左半部分、右半部分的最大子序和
const leftMax = divideAndConquer(nums, left, mid);
const rightMax = divideAndConquer(nums, mid + 1, right);
// 求解跨越中点的最大子序和
const crossMax = maxCrossingSum(nums, left, mid, right);
// 返回三者中的最大值
return Math.max(leftMax, rightMax, crossMax);
}
function maxCrossingSum(nums: number[], left: number, mid: number, right: number): number {
let leftSum = -Infinity;
let sum = 0;
// 从中点向左计算最大子序和
for (let i = mid; i >= left; i--) {
sum += nums[i];
leftSum = Math.max(leftSum, sum);
}
let rightSum = -Infinity;
sum = 0;
// 从中点向右计算最大子序和
for (let i = mid + 1; i <= right; i++) {
sum += nums[i];
rightSum = Math.max(rightSum, sum);
}
// 返回跨越中点的最大子序和
return leftSum + rightSum;
}
// 示例
const nums: number[] = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
console.log(maxSubArray(nums)); // 输出: 6 (子数组 [4, -1, 2, 1] 的和最大)
算法的时间复杂度为 O(n log n),由于把数组分成两半之后递归,因此空间复杂度为O(log n)。
解法二
运用动态规划求解。在遍历数组的过程中,判断 nums[i] 的值是否应该并入之前的序列 prev 中,如果 nums[i] > prev + nums[i],意味着 nums[i] 应该单独成为一个序列,否则该并入 prev 中形成一个比自己更大的序列。maxAn 则是从所有的序列和中取最大值。
ini
function maxSubArray(nums: number[]): number {
let maxAn = nums[0],prev = nums[0],i = 1
const len = nums.length
while(i < len) {
prev = Math.max(prev + nums[i],nums[i])
maxAn = Math.max(maxAn,prev)
i++
}
return maxAn
};
时间复杂度O(n),空间复杂度O(1)。这种方法避免了递归调用和栈空间的开销,将分治算法转化为迭代的形式,更容易理解和实现。