题目描述:给你一个整数数组
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 <= 10^5
-10^4 <= nums[i] <= 10^4
进阶:如果你已经实现复杂度为
O(n)
的解法,尝试使用更为精妙的 分治法 求解。
贪心算法的解释:
-
贪心选择: 在每一步中,我们都做出一个局部最优的选择,希望最终得到全局最优解。在最大子数组和问题中,每一步的贪心选择是决定是否将当前元素加入前面的子数组,还是从当前元素重新开始一个新的子数组.
这种贪心选择确保了在每一步我们都在试图构建一个尽可能大的子数组和,并且在整个遍历过程中保持对全局最优解(最大子数组和)的追踪。
动态规划的解释:
这种方法也可以看作是一种动态规划(因为我们在每一步都依赖之前的结果来做决策),但因为我们在每一步都只需要依赖上一步的状态,所以可以将这个算法实现为一个贪心算法。
- 贪心算法: 不需要显式的状态转移表(如 DP 数组),直接在遍历过程中更新最大和。
- 动态规划: 通常需要保存所有子问题的解,并根据这些解来构造最终的答案。
在这个问题中,贪心策略和动态规划方法是等价的。贪心的选择在每一步都能确保局部最优,并最终达到全局最优。
贪心代码:
class Solution {
public int maxSubArray(int[] nums) {
int max = nums[0];
int count = 0;
for (int num : nums) {
if (count < 0) {
count = num;
} else {
count += num;
}
max = Math.max(max, count);
}
return max;
}
}
代码思路:
- 如果当前子数组的和
count
小于 0,那么把当前元素作为新子数组的开始(因为如果继续累加一个负值,只会使和变得更小)。 - 如果当前子数组的和
count
大于等于 0,那么就继续累加当前元素。
显式DP实现:
class Solution {
public int maxSubArray(int[] nums) {
// dp[i] 表示以 nums[i] 结尾的最大子数组和
int[] dp = new int[nums.length];
dp[0] = nums[0];
int max = dp[0];
// 从第二个元素开始遍历
for (int i = 1; i < nums.length; i++) {
// dp[i] 要么是自己,要么是自己加上前面的最大子数组和
dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
// 更新全局最大子数组和
max = Math.max(max, dp[i]);
}
return max;
}
}
代码思路:
- 定义 DP 数组:
dp[i]
表示以nums[i]
结尾的最大子数组和。 - 初始化:
dp[0] = nums[0]
DP 数组的第一个元素直接等于nums[0]
,因为子数组只能包含自己。int max = dp[0];
初始化max
为第一个元素的值。 - DP 状态转移: 对于每个
i
(从1到nums.length - 1
):dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
- 如果
dp[i-1] + nums[i]
是负的,从当前元素重新开始一个新的子数组。 - 如果前一个子数组的和
dp[i-1]
加上当前元素nums[i]
是正的,说明可以扩展当前子数组并获得更大的和。
- 如果
- 更新最大值:在每一步都更新全局最大子数组和
max = Math.max(max, dp[i]);
。