动态规划学习与 LeetCode算法实践

想把一些基础算法重新捡起来,先复习一下动态规划。

动态规划主要的思想就是状态转移方程,该方程确保每一个状态都是最优解。

有的时候处理问题借助二维数组存储状态,有的时候可以简化为一维数组,如果不了解什么是状态转移方程可以去搜索一下,这里就不详细描述了,有点抽象,我刚开始学算法的时候,也不了解,感觉十分抽象,但是写了几道题目就发现,其实还是蛮好理解的,就是很多时候想不到方程应该怎么写。

来看几道LeetCode的题目:

1、

来源:LeetCode
链接:https://leetcode-cn.com/problems/minimum-path-sum

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

思考:设二维数组为grid,用动态规划做这道题,首先要去写出状态转移方程。

若要求出到达最后一个网格(grid[2][2])的最短路径,首先要求出到达grid[1][2]和到达grid[2][1]的最短路径,他们两个中间较小的那个,加上grid[2][2]即总路径最短。由此我们想到,到达grid[i][j]的最短路径,就是:

Min(grid[i-1][j], grid[i][j-1]) + grid[i][j]

当然上述式子的条件是i>=1,j>=1,否则会出现异常。

很明显,在i=0和j=0这两种情况下,最短路径只有一条。

所以我们可以写出状态转移方程:

i=0,j>0:

grid[0][j] = grid[0][j] + grid[0][j-1]

i>0,j=0:

grid[i][0] = grid[i][0] + grid[i-1][0];

i>0,j>0:

grid[i][j] = grid[i-1][j] > grid[i][j-1] ? grid[i][j] + grid[i][j-1] : grid[i][j] + grid[i-1][j];

写出方程,代码就很容易写了:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m=grid.size(); // row_num
        int n=grid[0].size(); //column_num
        
        for (int i=1; i<m; i++)
        {
            grid[i][0]=grid[i][0] + grid[i-1][0];
        }
        
        for (int j=1; j<n; j++)
        {
            grid[0][j]=grid[0][j] + grid[0][j-1];
        }
        
        for (int i=1; i<m; i++)
            for (int j=1; j<n; j++)
                grid[i][j] = grid[i-1][j] > grid[i][j-1] ? grid[i][j] + grid[i][j-1] : grid[i][j] + grid[i-1][j];
        
        return grid[m-1][n-1];
    }
};

2、

来源:LeetCode
链接:https://leetcode-cn.com/problems/triangle

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

例如,给定三角形:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]


]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

思考:这道题思路和上面一题差不多,本质是一样的,还是通过二维数组来存贮状态进行解决,就直接贴代码了,很容易看懂。

首先对角线和最左边一列的状态是固定的,对角线的路径只有一条,最左边一列的状态也是一条。

triangle[i][0] = triangle[i-1][0] + triangle[i][0];

triangle[i][i] = triangle[i-1][i-1] + triangle[i][i];

剩下的就是其他地方的状态了:从triangle[2][1]开始看,它可以是从triangle[1][0]往下走,也可以是从triangle[1][1]往下走

那么就是:triangle[i][j] = triangle[i-1][j-1] > triangle[i-1][j] ? triangle[i][j] + triangle[i-1][j] : triangle[i][j] + triangle[i-1][j-1]

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int depth = triangle.size();
        
        for (int i=1; i<depth; i++)
        {
            triangle[i][0] = triangle[i-1][0] + triangle[i][0];
            triangle[i][i] = triangle[i-1][i-1] + triangle[i][i];
        }
        int min = triangle[depth-1][depth-1];
            
        
        for (int i=2; i<depth; i++)
            for (int j=1; j<i; j++)
                triangle[i][j] = triangle[i-1][j-1] > triangle[i-1][j] ? triangle[i][j] + triangle[i-1][j] : triangle[i][j] + triangle[i-1][j-1];
        
        for (int i=0; i<depth; i++)
            min = triangle[depth-1][i] > min ? min : triangle[depth-1][i];
        
        return min;
        
    }
};

3、

来源:LeetCode
链接:https://leetcode-cn.com/problems/maximum-product-subarray

给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。

示例 1:

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:

输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

思考:这道题的思路有点厉害,我首先想到的是用一个二维数组来进行状态存储,这样结果是可以出来的,但是提交却报错了,我仔细看了一下,说是用二维数组存储状态,其实就是暴力法........不能算是动态规划......想了很久想不出,参考了一位大佬的思路:因为这里是乘法,会出现负负得正的情况,即本来绝对值很大的一个负数,乘了一个负数就变成了最大值,因此这里需要去存储(维护)三个值,即在计算Nums[i]的时候,需要知道nums[i-1]这里的最大值和最小值:

max_nums_i = Max(Max(pre_max * nums[i], max_res), pre_min * nums[i])

min_nums_i = Min(Min(pre_max * nums[i], max_res), pre_min * nums[i])

max = Max(max, max_nums_i)

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int length = nums.size();
        int i=0;
        int pmax = nums[0],tmax = nums[0];
        int pmin = nums[0],tmin = nums[0];
        int max = nums[0];
        
        for (i=0; i<length; i++)
        {
            if ( i!=0 )
            {
                tmax = pmax;
                tmin = pmin;
                
                pmax = tmax * nums[i] > nums[i] ? tmax * nums[i] : nums[i];
                pmax = (tmin * nums[i] > nums[i] ? tmin * nums[i] : nums[i]) > pmax ? (tmin * nums[i] > nums[i] ? tmin * nums[i] : nums[i]) : pmax;
                
                pmin = tmax * nums[i] < nums[i] ? tmax * nums[i] : nums[i];
                pmin = (tmin * nums[i] < nums[i] ? tmin * nums[i] : nums[i]) < pmin ? (tmin * nums[i] < nums[i] ? tmin * nums[i] : nums[i]) : pmin;
                
                max = max > pmax ? max : pmax;
                
            }

        }
        
        return max;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值