想把一些基础算法重新捡起来,先复习一下动态规划。
动态规划主要的思想就是状态转移方程,该方程确保每一个状态都是最优解。
有的时候处理问题借助二维数组存储状态,有的时候可以简化为一维数组,如果不了解什么是状态转移方程可以去搜索一下,这里就不详细描述了,有点抽象,我刚开始学算法的时候,也不了解,感觉十分抽象,但是写了几道题目就发现,其实还是蛮好理解的,就是很多时候想不到方程应该怎么写。
来看几道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;
}
};