详细解释算法之<动态规划>

题目:最大子数组和

给你一个整数数组 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

leetcode原题链接

 

关于动态规划

解答这类题目, 省略不掉遍历, 因此我们先从遍历方式说起

以 [a, b , c, d , e]为例,通常我们遍历子串或者子序列有三种遍历方式——

1、以某个节点为开头的所有子序列: 如 [a],[a, b],[ a, b, c] ... 再从以 b 为开头的子序列开始遍历 [b] [b, c]。

2、根据子序列的长度为标杆,如先遍历出子序列长度为 1 的子序列,在遍历出长度为 2 的 等等。

3、以子序列的结束节点为基准,先遍历出以某个节点为结束的所有子序列,因为每个节点都可能会是子序列的结束节点,因此要遍历下整个序列,如: 以 b 为结束点的所有子序列: [a , b] [b] 以 c 为结束点的所有子序列: [a, b, c] [b, c] [ c ]。

第一种遍历方式通常用于暴力解法,

第二种遍历方式 leetcode (5. 最长回文子串 ) 中的解法就用到了。

第三种遍历方式 因为可以产生递推关系, 采用动态规划时, 经常通过此种遍历方式, 如 背包问题, 最大公共子串 , 这里的动态规划解法也是以 先遍历出< 以某个节点为结束节点 >的所有子序列 的思路。

对于刚接触动态规划的, 我感觉熟悉第三种遍历方式是需要抓住的核心。

因为我们通常的惯性思维是以子序列的开头为基准,先遍历出以 a 为开头的所有子序列,再遍历出以 b 为开头的...但是动态规划为了找到不同子序列之间的递推关系,恰恰是以子序列的结束点为基准的,这点开阔了我们的思路。

我在网上看不少解答时,直接阅读其代码,总是感觉很理解很吃力,因为好多没有写清楚,一些遍历到底代表什么意思,看了许久仍不知所以然,下面的代码中摘录了 维基中的解释,感觉比较清楚,供大家理解参考。

代码:

// Kadane算法扫描一次整个数列的所有数值,
// 在每一个扫描点计算以该点数值为结束点的子数列的最大和(正数和)。
// 该子数列由两部分组成:以前一个位置为结束点的最大子数列、该位置的数值。
// 因为该算法用到了“最佳子结构”(以每个位置为终点的最大子数列都是基于其前一位置的最大子数列计算得出, 
// 该算法可看成动态规划的一个例子。
// 状态转移方程:sum[i] = max{sum[i-1]+a[i],a[i]}   
// 其中(sum[i]记录以a[i]为子序列末端的最大序子列连续和)

function  maxSubArray2  ( nums ) {
    if (!nums.length) {
        return;
    };
    // 在每一个扫描点计算以该点数值为结束点的子数列的最大和(正数和)。
    let max_ending_here = nums[0];
    let max_so_far = nums[0];

    for (let i = 1; i < nums.length; i ++ ) {
        // 以每个位置为终点的最大子数列 都是基于其前一位置的最大子数列计算得出,

        max_ending_here = Math.max ( nums[i], max_ending_here + nums[i]);
        max_so_far = Math.max ( max_so_far, max_ending_here);
    };

    return max_so_far;
};

题解:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if(nums.size()<1)
            return 0;
        
        int sum = nums[0] , maxSum = nums[0] ;

        // 遍历数组,在每一个扫描点计算以该元素为结尾的子串的和
        for(int i=1;i<nums.size();i++)
        {
            // 判断子串延续到当前节点的收益。
            // 若收益为负(即sum+nums[i]<nums[i]),则舍弃之前的子串,以当前节点为起始重新开始计算
            // 这一句也可以写作 if(sum < 0)
            if(sum + nums[i] < nums[i])
            {
                sum = nums[i];
            }
            else
            {
                sum += nums[i];
            }
            // 记录最大和
            maxSum = max(maxSum,sum);
        }
        return maxSum;
    }
};

 

第二块代码和 第一块代码 思路实现是完全一样的,但是如果第一次看到这类题目,直接阅读 第二块代码,理解起来很难,尤其是 如果改成 if (sum > 0 ) 对于刚接触的这题目的比较不好理解。

 

原文链接

 

posted @ 2021-12-28 16:25  暴躁老砚  阅读(40)  评论(0编辑  收藏  举报