总结动态规划
如果觉得好请点赞收藏
首先动态规划存在重叠子问题这时候就需要备忘录(就是子问题得到结果先不要返回,先放到备忘录中,下次计算先在备忘录里找,找到了的就可以直接用)或者DP table来优化穷举的过程.避免不必要的计算.
举个例子就是说斐波那契数列
的优化.
而且,动态规划问题一定会具有最优子结构
解决思路如下
ps: 凡是需要涉及到递归的问题的时候,最好都要画出递归树,递归算法的时间复杂度等于子问题个数乘以解决一个子问题所需要的时间
带备忘录的递归写法是自顶向下也就是说(递归图)从上往下延伸.eg求f(20)就往下一直进行规模分解,直到f(1).f(0)触底.==然后逐层返回答案=
而动态规划的思路是自底向上,就是反过来,由规模最小的子问题f(1),f(0)往上推.这也就是为什么动态规划一般都脱离了递归,而是由循环迭代完成计算
其实,带备忘录的递归,与动态规划迭代差不多就是一样的. 在做递归的问题上一定要画出递归树
[状态] 状态就是原问题和子问题中的变量,变量的个数决定了dp数组的维数
[选择] 选择就是也就是对于每个状态,可以做出什么选择改变当前状态。
[遍历] 遍历可以从前往后,也可以从后往前,也可以斜着遍历。
比较关键的是要在择优中把状态转移方程找出来
其实,动态规划就是暴力穷举的基础上就行备忘录优化的从底向上版本
举例:礼物的最大价值
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
问题分析
第一步判定是否具有最优子结构
以例子来说.我在[1][1]取得最大礼物价值其实就是比较[0][1]和[1][0]处取得的最大值,这两者之间并没有制约,所以相互独立.这是一个动态规划的问题.当没有最优子结构的时候那就改造问题.最优子结构作为动态规划问题的必要条件,一定是让你求最值的
第二步确定状态
在[2][2]处和[0][1]出取得最大值,也就是说在原问题和子问题之间的变量就是i和j.目标就是最大的值
第三步确定dp数组/表的含义
由上可知dp[i][j]代表在[i][j]处获得礼物的最大值.大小与棋盘大小一致.
第四步明确选择并且择优
举例子:
我在[1][1]处去的的最大值,可以根据我做出的选择而发生改变;这里的值有两种方式改变dp[0][1]+grid[1][1]以及dp[1][0]+grid[1][1];因为数组不能越界所以要加限制条件
择优:取两者中大的;
列出状态转移方程;
dp[i][j]=max(dp[i-1][j]+grid[i][j],dp[i][j-1]+grid[i][j])
考虑到数组越界问题所以得到
continue 当i=0,j=0;
dp[i][j]=dp[i-1][j]+grid[i][j] 当i!=0,j==0
dp[i][j]=dp[i][j-1]+grid[i][j] 当i=0,j!=0
dp[i][j]=max(dp[i-1][j]+grid[i][j],dp[i][j-1]+grid[i][j]) 当i!=0,j!=0
所以写出代码
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
int row=grid.size();
int col=grid[0].size();
vector<vector<int>> dp(row,vector<int>(col,0));
dp[0][0]=grid[0][0];
for(int i=0;i<row;i++)
{
for(int j=0;j<col;j++)
{
if(i==0&&j==0) continue;
if(i==0&&j!=0) dp[i][j]=dp[i][j-1]+grid[i][j];
if(j==0&&i!=0) dp[i][j]=dp[i-1][j]+grid[i][j];
if(i!=0&&j!=0) dp[i][j]=max(dp[i][j-1]+grid[i][j],dp[i-1][j]+grid[i][j]);
}
}
return dp[row-1][col-1];
}
};
举例 股票的最大利润
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
分析;
[变量]原问题在这个6个数中的最大利润;子问题 5个数 4个数中的最大利润,所以变量是:个数.目标:最大利润
[dp数组] : 所以dp[i]代表着以i结束的数中最大的利润;大小与输入price个数一致.
[选择择优] :对于dp[2]是怎么得到的,或者说怎么可以改变dp[2].dp[2]可以由dp[1]来;也可以由price[1]-minprice{0:1}//左闭右开
所以状态转移矩阵是
dp[i]=max(dp[i-1],price[i]-min{0,i});
[base case] : dp[0]=-price[i]
所以写出程序
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n==0) return 0;
vector<int> dp(n,0);
dp[0]=-prices[0];
int minprice=prices[0];
for(int i=1;i<n;i++)
{
dp[i]=max(dp[i-1],prices[i]-minprice);
minprice=min(minprice,prices[i]);
}
return (dp[n-1]<0)? 0:dp[n-1];
}
};
补充一下动态规划类问题的思路步骤:
暴力的深度优先搜索
画出/思考出问题和子问题的关系,看有没有重复子问题
如果有重复子问题,考虑增加记忆化的数据结构
据此,思考动态规划的状态和递推方程
实现动态规划