【LeetCode 热题 100】53. 最大子数组和——(解法二)动态规划

Problem: 53. 最大子数组和
题目:给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。

LeetCode 热题 100】53. 最大子数组和——(解法一)前缀和

整体思路

这段代码同样旨在解决 “最大子数组和” 问题。它采用的是一种非常经典且标准的 动态规划(Dynamic Programming) 方法,通常被称为 Kadane’s 算法 的一种实现形式。

算法的核心思想是定义一个状态 dp[i],并找出状态之间的转移关系。

  1. 状态定义

    • dp[i] 被定义为:nums[i] 结尾的所有连续子数组中,和最大的是多少。
    • 这个定义是算法的关键。它关注的是以特定位置为终点的子数组,而不是全局的所有子数组。
  2. 状态转移方程

    • 对于 dp[i],我们有两种选择来构成以 nums[i] 结尾的最大和子数组:
      a. 自成一派:子数组只包含 nums[i] 本身。
      b. 与前面合并:将 nums[i] 接在以 nums[i-1] 结尾的最大和子数组(即 dp[i-1])的后面。
    • 我们应该选择哪种呢?这取决于 dp[i-1] 的值:
      • 如果 dp[i-1] > 0,说明前面的子数组和是正数,将其与 nums[i] 合并会使和增大。此时 dp[i] = dp[i-1] + nums[i]
      • 如果 dp[i-1] <= 0,说明前面的子数组和是负数或零,将其与 nums[i] 合并只会拖后腿,不如让 nums[i] 自己重新开始。此时 dp[i] = nums[i]
    • 综合以上两种情况,我们可以得到状态转移方程:
      dp[i] = Math.max(dp[i-1], 0) + nums[i]
      或者等价地写成:
      dp[i] = nums[i] + (dp[i-1] > 0 ? dp[i-1] : 0)
  3. 最终结果

    • 在计算出所有 dp[i] 的值之后,全局的最大子数组和 ans 就是所有 dp[i] 值中的最大值。
    • 这是因为全局的最大子数组必然会以某个 nums[i] 为结尾,所以它一定在 dp[0], dp[1], ..., dp[n-1] 中出现过。
  4. 算法步骤

    • 创建一个 dp 数组来存储每个状态的值。
    • 基础情况dp[0] 只能是 nums[0] 本身,因为以 nums[0] 结尾的子数组只有一个。
    • 迭代计算:通过一个 for 循环,从 i=1 开始,利用状态转移方程计算出所有的 dp[i]
    • 记录全局最大值:在每次计算出 dp[i] 后,都用它来更新全局最大值 ans
    • 返回 ans

完整代码

class Solution {
    /**
     * 找到一个具有最大和的连续子数组,并返回其和。
     * @param nums 整数数组
     * @return 最大子数组和
     */
    public int maxSubArray(int[] nums) {
        // ans: 用于存储最终结果,即全局的最大子数组和。
        int ans = 0;
        int n = nums.length;
        
        // dp 数组:dp[i] 表示以 nums[i] 结尾的连续子数组的最大和。
        int[] dp = new int[n];
        
        // 基础情况:以 nums[0] 结尾的子数组只有一个,即 nums[0] 本身。
        dp[0] = nums[0];
        // 初始化 ans 为 dp[0],因为至少有一个子数组。
        ans = dp[0];
        
        // 从第二个元素开始,应用状态转移方程
        for (int i = 1; i < n; i++) {
            // 状态转移方程:
            // dp[i] 的值取决于以 nums[i-1] 结尾的最大和 (dp[i-1]) 是否为正。
            // 如果 dp[i-1] > 0,则将其加到 nums[i] 上,可以使和变大。
            // 如果 dp[i-1] <= 0,则将其舍弃,从 nums[i] 重新开始。
            // Math.max(dp[i - 1], 0) 巧妙地实现了这一逻辑。
            dp[i] = Math.max(dp[i - 1], 0) + nums[i];
            
            // 全局最大和 ans 是所有 dp[i] 中的最大值。
            // 在每次计算出 dp[i] 后,都用它来更新 ans。
            ans = Math.max(ans, dp[i]);
        }
        
        // 返回计算出的全局最大子数组和
        return ans;
    }
}

时空复杂度

时间复杂度:O(N)

  1. 循环:算法的核心是一个 for 循环,它从 i=1 遍历到 n-1。算上初始化的 dp[0],整个 nums 数组被访问了一次。循环执行了 N-1 次。
  2. 循环内部操作
    • 在循环的每一次迭代中,执行的都是基本的数组访问、Math.max 和加法运算。
    • 这些操作的时间复杂度都是 O(1)

综合分析
算法由 N 次 O(1) 的操作组成。因此,总的时间复杂度是 N * O(1) = O(N)

空间复杂度:O(N)

  1. 主要存储开销:算法创建了一个名为 dp 的整型数组来存储动态规划的状态。
  2. 空间大小:该数组的长度与输入数组 nums 的长度 N 相同。
  3. 其他变量ans, n, i 等变量都只占用常数级别的空间,即 O(1)。

综合分析
算法所需的额外空间主要由 dp 数组决定。因此,其空间复杂度为 O(N)

空间优化提示
观察状态转移方程 dp[i] = Math.max(dp[i - 1], 0) + nums[i],可以发现 dp[i] 的计算只依赖于 dp[i-1]。因此,我们不需要存储整个 dp 数组,只需一个变量来记录前一个状态即可。这样可以将空间复杂度优化到 O(1)

参考灵神

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xumistore

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值