【LeetCode】剑指 Offer 42. 连续子数组的最大和 p218 -- Java Version

题目链接:https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/

1. 题目介绍(42. 连续子数组的最大和)

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

【测试用例】:
示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

【条件约束】:

提示

  • 1 <= arr.length <= 10^5
  • -100 <= arr[i] <= 100

【相关题目】:

注意:本题与主站 【LeetCode】No.53. 最大子数组和 – Java Version 题目相同。

2. 题解

2.1 枚举 – O(n2)

时间复杂度O(n2),空间复杂度O(1)

解题思路】:
遇事不决,暴力枚举,最直接的方法,枚举数组的所有子数组 并求出它们的和。不过不知道是不是最近干力活干多了,我一开始想的是 1. 先分出所有子数组;2. 遍历并比较所有子数组,找出最大。如果题目要求的是 返回 所有符合条件的子数组 这类的使用这种方法还是比较合理,如果只是像这道题一样,返回仅仅是最大值,这就属实没有必要。
……
实现策略】:

  1. 定义一个双重循环,循环遍历每一种子数组的可能;
  2. 定义当前累加和变量 sum 以及最大和 maxSum,一旦发现当前累加和大于了最大和,就修改最大和为累加和;
  3. 循环结束,返回结果。
class Solution {
    
    
    // Soulution1:枚举所有子数组和
    public int maxSubArray(int[] nums) {
    
    
        // 定义变量 n 记录数组长度
        int n = nums.length;
        // 定义变量 maxSum 记录最大子数组和
        int maxSum = Integer.MIN_VALUE;
        // 双重循环,依次遍历所有可能的子数组
        for (int i = 0; i < n; i++)
        {
    
    
            int sum = 0;
            for (int j = i; j < n; j++)
            {
    
    
                sum = sum + nums[j];
                // 如果当前子数组的和大于 maxSum,则让其成为最大
                if (sum > maxSum)
                {
    
    
                    maxSum = sum;
                }
            }
        }
        // 最后返回结果
        return maxSum;
    }
}

在这里插入图片描述

2.2 举例分析数组的规律(原书题解1)-- O(n)

时间复杂度O(n),空间复杂度O(1)

解题思路】:
试着从头到尾逐个累加示例数组中的每个数字,初始化和为 0。第一步加上第一个数字 1,此时和为 1。第二部加上数字 -2,和就变成了 -1。第三步加上数字 3,我们注意到由于此前累加的和是 -1,小于 0,那么如果用 -1 加上3,得到的和是 2,比 3 本身还要小。因此,我们不用考虑从第一个数字开始的子数组,之前累加的和也被抛弃。
通过举例,我们就能找到如下规律:

  1. 从第一个元素 遍历累加开始,如果遇到 累加和小于等于0 的情况(遇到负数,减小了累加和),说明此时的子数组必然不可能是最大,可将其抛弃;
  2. 抛弃前面的累加和后,遍历到的当前值重新出发,直到遍历结束,得出最大和。

……
实现策略】:

  1. 如解题思路所示,一次遍历,当当前累加和 sum 小于等于 0时,抛弃前累加和,仅将其赋值为当前数字;
  2. 如果累加和大于0,则让其继续累加;
  3. 在遍历的过程中,一旦发现当前累加和大于了最大和 maxSum,就修改最大和的值为当前累加和。
class Solution {
    
    
    // Soulution2:一次遍历
    public int maxSubArray(int[] nums) {
    
    
        // 判断无效输入
        if (nums.length <= 0) return -1;
        // 定义变量 n 记录数组长度
        int n = nums.length;
        // 定义累加和 sum 与 最大和 maxSum
        int sum = 0;
        int maxSum = Integer.MIN_VALUE;
        // 开始循环
        for (int i = 0; i < n; i++){
    
    
            // 如果当前 累加和 小于等于0,丢弃该值,重新开始
            if (sum <= 0) sum = nums[i];
            else sum += nums[i];
            // 如果在循环的过程中出现了更大值,则修改最大和
            if (sum > maxSum) maxSum = sum;
        }
        return maxSum;
    }
}

在这里插入图片描述

2.3 动态规划(原书题解2)-- O(n)

时间复杂度O(n),空间复杂度O(1)
在这里插入图片描述

解题思路】:
关于什么时候用DP?

  • 首先你要了解 回溯法 ,毕竟是 DP 的起源,在你熟悉 回溯 之后,你发现这道题它好像可以用 回溯 去做,或者发现你想遍历它来给出答案,但是答案要求的并不是把所有满足的解法都列出来,而是只需要给出一共有几种解法,这时候一般就可以考虑使用 DP

……
如上图公式所示:

  • 我们可以用函数 f(i) 来表示以第 i 个数字结尾的子数组的最大和,那么我们需要求出 max[f(i)],其中 0 <= i < n.;
  • 当以第 i-1 个数字结尾的子数组的数字和小于0时,把这个负数与第 i 个数累加,得到的结果会比第 i 个数字本身还要小,所以这种情况下以第 i 个数字结尾的子数组就是第 i 个数字本身;
  • 如果以第 i-1 个数字结尾的子数组的数字和大于0,则与第 i 个数字累加就得到以第 i 个数字结尾的子数组中所有数字的和。

……
实现策略】:
动态规划属于后一状态依赖前一状态的值,在此处,dp[i] 仅与 dp[i-1] 有关。在本题中使用动态规划的思想解决该题,代码和 2.2 几乎一致,其实说白了就是找关系,只不过难点在于如何找到 从前一个状态转化到后一个状态之间的递推关系

class Solution {
    
    
    public int maxSubArray(int[] nums) {
    
    
        int count = nums.length;
        int sum = nums[0],dp=nums[0];
        for(int i = 1; i < count; i++)
        {
    
    
            if(dp > 0) dp += nums[i];
            else dp = nums[i];
            sum = Math.max(sum,dp);
        }
        return sum;
    }
}

在这里插入图片描述

3. 参考资料

[1] 面试题42. 连续子数组的最大和(动态规划,清晰图解)
[2] 如果第一时间想到dp的话1分钟写完
[3] 动态规划算法入门

猜你喜欢

转载自blog.csdn.net/qq_41071754/article/details/129875105