题目

类型:动态规划
image.png

解题思路

因为子数组一定是连续的,并不能保证 nums[0..i] 中的最大子数组与 nums[i+1] 是相邻的,也就没办法从 dp[i] 推导出 dp[i+1]。
依然使用数学归纳法来找状态转移关系:假设我们已经算出了 dp[i-1],如何推导出 dp[i] 呢?
可以做到,dp[i] 有两种「选择」,要么与前面的相邻子数组连接,形成一个和更大的子数组;要么不与前面的子数组连接,自成一派,自己作为一个子数组。
如何选择?既然要求「最大子数组和」,当然选择结果更大的那个啦:

  1. // 要么自成一派,要么和前面的子数组合并
  2. dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);

综上,我们已经写出了状态转移方程,就可以直接写出解法了:

  1. int maxSubArray(int[] nums) {
  2. int n = nums.length;
  3. if (n == 0) return 0;
  4. // 定义:dp[i] 记录以 nums[i] 为结尾的「最大子数组和」
  5. int[] dp = new int[n];
  6. // base case
  7. // 第一个元素前面没有子数组
  8. dp[0] = nums[0];
  9. // 状态转移方程
  10. for (int i = 1; i < n; i++) {
  11. dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);
  12. }
  13. // 得到 nums 的最大子数组
  14. int res = Integer.MIN_VALUE;
  15. for (int i = 0; i < n; i++) {
  16. res = Math.max(res, dp[i]);
  17. }
  18. return res;
  19. }

以上解法时间复杂度是 O(N),空间复杂度也是 O(N),较暴力解法已经很优秀了,不过注意到 dp[i] 仅仅和 dp[i-1] 的状态有关,那么我们可以施展前文 动态规划的降维打击:空间压缩技巧 讲的技巧进行进一步优化,将空间复杂度降低:

  1. int maxSubArray(int[] nums) {
  2. int n = nums.length;
  3. if (n == 0) return 0;
  4. // base case
  5. int dp_0 = nums[0];
  6. int dp_1 = 0, res = dp_0;
  7. for (int i = 1; i < n; i++) {
  8. // dp[i] = max(nums[i], nums[i] + dp[i-1])
  9. dp_1 = Math.max(nums[i], nums[i] + dp_0);
  10. dp_0 = dp_1;
  11. // 顺便计算最大的结果
  12. res = Math.max(res, dp_1);
  13. }
  14. return res;
  15. }

代码

  1. class Solution {
  2. public int maxSubArray(int[] nums) {
  3. int pre = 0, maxAns = nums[0];
  4. for (int x : nums) {
  5. pre = Math.max(pre + x, x);
  6. maxAns = Math.max(maxAns, pre);
  7. }
  8. return maxAns;
  9. }
  10. }