动态规划题解——给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和

动态规划题解

1. 定义子问题|定义状态

把不确定的因素确定下来,进而把子问题定义清楚,把子问题定义得简单。动态规划的思想通过解决了一个一个简单的问题,进而把简单的问题的解组成了复杂的问题的解。

我们 不知道和最大的连续子数组一定会选哪一个数,那么我们可以求出 所有 经过输入数组的某一个数的连续子数组的最大和。

例如,示例 1 输入数组是 [-2,1,-3],我们可以求出以下子问题:

  • 子问题1,经过-2的连续子数组的最大和是多少
  • 子问题2,经过1的连续子数组的最大和是多少
  • 子问题3,经过-3的连续子数组的最大和是多少

一共有三个子问题。这些子问题之间的联系并没有那么好看出来,这是因为 子问题的描述还有不确定的地方

例如子问题2,经过1的连续子数组的最大和可能是:

  • -2+1 ->-1,1是连续数组的第二个元素
  • -2+1±3 ->-4,是连续数组的第二个元素
  • 1±3 ->-2,是连续数组的第一个元素

我们不确定的是:1 是连续子数组的第几个元素。那么我们就把 1定义成连续子数组的最后一个元素。

我们可以求出以下子问题:

  • 子问题1,-2结尾的连续子数组的最大和是多少
  • 子问题2,1结尾的连续子数组的最大和是多少
  • 子问题3,-3结尾的连续子数组的最大和是多少

我们加上了「结尾的」,这些子问题之间就有了联系

单独看子问题1和子问题2:

  • 子问题1,以 −2结尾的**连续子数组的最大和是多少;
以 −2 结尾的连续子数组是 [-2],因此最大和就是 −2。
  • 子问题 2:以 11 结尾的连续子数组的最大和是多少;
以 1 结尾的连续子数组有 [-2,1] 和 [1] ,其中 [-2,1] 就是在「子问题 1」的后面加上 1 得到。−2+1=−1<1−2+1=−1<1 ,因此「子问题 2」 的答案是 1

如果编号为 i 的子问题的结果是负数或者 00 ,那么编号为 i + 1 的子问题就可以把编号为 i 的子问题的结果舍弃掉(这里 i 为整数,最小值为 1 ,最大值为8),这是因为:

  • 一个数 a 加上负数的结果比 a 更小;
  • 一个数 a 加上 0 的结果不会比 a 更大;
  • 而子问题的定义必须以一个数结尾,因此如果子问题 i 的结果是负数或者 0,那么子问题i + 1 的答案就是以 nums[i] 结尾的那个数。

2. 步骤

①定义状态(定义子问题)

dp[i]:表示以 nums[i] 结尾 的 连续 子数组的最大和。
其中结尾 和 连续是关键字

②状态转移方程(描述子问题之间的联系)

根据状态的定义,由于 nums[i] 一定会被选取,并且以 nums[i] 结尾的连续子数组与以nums[i - 1] 结尾的连续子数组只相差一个元素 nums[i]。
假设nums严格大于零,那么一定有dp[i] = dp[i-1] + nums[i]
但是dp[i - 1] 可能是负数,分类谈论:
1. dp[i-1]>0,dp[i] = dp[i-1]+nums[i]
2.dp[i-1]<=0,那么 nums[i] 加上前面的数 dp[i - 1] 以后值不会变,大。于是 dp[i] 「另起炉灶」,此时单独的一个 nums[i] 的值,就是 dp[i]。

以上两种情况的最大值就是dp[i]的值,如下转移方程:

dp[i] = max{dp[i-1]+nums[i],nums[i]}

③初始化(定义一开始的最大值)

dp[0] 根据定义,只有 1 个数,一定以 nums[0] 结尾,因此 dp[0] = nums[0]

④输出

这个问题的输出是把所有的 dp[0]dp[1]、……、dp[n - 1] 都看一遍,取最大值。

3.代码

public class Solution {

    public int maxSubArray(int[] nums) {
        int len = nums.length;
        // dp[i] 表示:以 nums[i] 结尾的连续子数组的最大和
        int[] dp = new int[len];
        dp[0] = nums[0];

        for (int i = 1; i < len; i++) {
            if (dp[i - 1] > 0) {
                dp[i] = dp[i - 1] + nums[i];
            } else {
                dp[i] = nums[i];
            }
        }

        // 也可以在上面遍历的同时求出 res 的最大值,这里我们为了语义清晰分开写,大家可以自行选择
        int res = dp[0];
        for (int i = 1; i < len; i++) {
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}
/*1.定义子问题:以数组中每个元素,即nums[i]为结尾的连续子数组最大和
    2.状态转移方程:dp[i] = max{dp[i-1]+nums[i],nums[i]}
    3.初始化,dp[0] = nums[0],初始就是nums[0]的子问题的解
    4.输出(需要遍历每一个子问题,然后得到最大的和)
*/
int maxSubArray(int* nums, int numsSize){
    int *dp = (int *)malloc(sizeof(int)*numsSize);
    //初始化
    dp[0] = nums[0];
    int i;int res = nums[0];
    for(i=1;i<numsSize;i++){
        if(dp[i-1]>0){
            dp[i] = dp[i-1]+nums[i]; 
        }else{
            dp[i] = nums[i];
        }
        if(res < dp[i]){
            res = dp[i];
        }
    }
    return res;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值