前言:最近做了一些国内大厂的笔试,发现国内大厂很喜欢考动态规划。这篇文章记录一下自己对动态规划的学习。
计划在26号之前练习二十道左右的DP题,题的来源选择leetcode吧,难度主要在在medium和hard之间。
针对几个比较经典的题目,会写具体的文章去分析。
动态规划一般解决什么问题?
1.计数
- 有多少种方式走到右下角
- 有多少种方法选出k个数使得和是sum
2.求最大最小值
- 从左上角走到右下角路径的最大数字和
- 最长上升子序列长度
3.求存在性
- 取石子(取书)游戏,先手是否必胜
- 能不能选出k个数使得和是Sum
我们先以coin change这个题目来举例分析动态规划的一般解题思路
动态规划组成部分一:确定状态
- 状态是定海神针
- 简单的说,解决动态规划的时候一般需要开一个数组,数组的每个元素F[i]或者F[i][j]代表什么
- 确定状态需要两个意识
1.最后一步
2.子问题
动态规划组成部分二:转移方程
- 设状态F[x] = 最少用多少枚硬币拼出x
- 对任意x,F[x] = min{F[x-2]+1,F[x-5]+1,F[x-7]+1}
动态规划组成部分三:初始条件和边界条件
- x-2,x-5或者x-7小于0怎么办?什么时候停下?
- 如果拼不出Y,怎么定义F[Y],(可以考虑定义为F[Y]=amount+1) 最后如果F[x]=amount+1说明拼不出来
- 还有比如F(1)=min{F[-1]+1,F[-4]+1,F[-6]+1},此时是因为1块钱太少,没法用拼出来。说明我们在每一步dp前要判断进入状态转移的条件是否满足,不满足就不进行状态转移的操作。
- 初始条件F[0]=0
动态规划组成部分四:计算顺序
- 每一步尝试三种硬币,一种27步
- 与递归算法相比,没有任何重复计算
- 算法时间复杂度:27*3 如果要拼成的金额为n,有m种硬币,那么时间复杂度为m*n
coin change(leetcode300)代码如下
class Solution {
public int coinChange(int[] coins, int amount) {
//corner case
if(coins==null||coins.length==0)return 0;
int []dp = new int[amount+1];
Arrays.fill(dp,amount+1);
dp[0]=0;
for(int i=1;i<=amount;i++){
for(int j=0;j<coins.length;j++){
if(i-coins[j]>=0){
dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
}
}
}
return dp[amount]==amount+1? -1:dp[amount];
}
}