动态规划视频链接
动态规划
总结的非常全的各类动态规划问题,一定要看
LintCode阶梯训练,必须先完成上一节课的题目,才能继续下一节课。
LintCode阶梯训练
哪个题目是动态规划
例子1
给定一个矩阵网格,一个机器人从左上角出发,每次可以向下或者向右下走一步
- 题A
求有多少种方式走到右下角 - 题B
输出所有走到右下角的路径
题A可以用动态规划,题B不可以。
动态规划题目特点
-
1、计数
有多少种方式走到右下角。
有多少种方法选出k个数使得和为sum。 -
2、求最大最小值
从左上角到右下角路径的最大数字和
最长上升子序列长度 -
3、求存在性
取石子游戏,先手是否必胜
能不能选出k个数使得和为Sum
例题
给出不同面额的硬币以及一个总金额. 写一个方法来计算给出的总金额可以换取的最少的硬币数量. 如果已有硬币的任意组合均无法与总金额面额相等, 那么返回 -1.
最小硬币组合–》尽量用面值大的硬币。
动态规划解题步骤
确定状态
状态在动态规划中的作用属于定海神针
简单的说,解动态规划的时候需要开一个数组,数组的每个元素f[i]或者f[i][j]代表什么,类似于解数学题,X、Y、Z代表什么
确定状态需要两个意识
- 最后一步
虽然我们不知道最有策略是什么,但是最优策略肯定是K枚硬币a1,a2,a3,…ak面值加起来为Sum
最后一步即最优策略的最后一步,即这里面的最后一块硬币ak
除了这个硬币,前面硬币的面值加起来是sum-ak,如果sum为27
对于拼出27-ak的个数一定还是最优的。
所以我们就要求:最少用多少枚硬币可以拼出27-ak。
原问题是最少用多少枚硬币拼出27
我们将问题转化成了一个子问题,而且规模更小:27-ak
为了简化定义,我们就假设状态f(X)=最少用多少枚硬币拼出X - 子问题
等等,我们还不知道最后那枚硬币ak是多少
最后那个硬币ak只可能是2、5、7
如果硬币ak是2,f(27)应该是f(27-2)+1,加1因为要加上这块硬币2
如果硬币ak是5,f(27)应该是f(27-5)+1,加1因为要加上这块硬币5
如果硬币ak是7,f(27)应该是f(27-7)+1,加1因为要加上这块硬币7
除此之外,就没有其他可能了
需要最少的硬币数,所以
f(27)=min{f(27-1)+1,f(27-5)+1,f(27-7)+1}
转移方程
设状态f[x] = 最少用多少枚硬币拼出x。
对于任意x
- f[x] = min{f[x-2]+1,f[x-5]+1,f[x-7]+1}
初始条件和边界情况
- 初始条件指的是用转移方程算不出来的,但是我们又需要它的定义的。
计算顺序
递归解法
int f(int x)
{
if(x==0)
return 0;
int res = MAX_VALUE;
if(x>=2)
{
res = Math.min(f(x-2)+1,res);
}
if(x>=5)
{
res = Math.min(f(x-5)+1,res);
}
if(x>=7)
{
res = Math.min(f(x-7)+1,res);
}
return res;
}
- 递归做了很多重复计算,效率低下
- 如何避免?
- 将结果保存下来,并改变计算顺序。
小结
DP解决方法
从集合角度考虑DP问题
动态规划分析的两个阶段
状态表示
- 状态所表示的集合
状态计算
例子1
给出不同面额的硬币以及一个总金额. 写一个方法来计算给出的总金额可以换取的最少的硬币数量. 如果已有硬币的任意组合均无法与总金额面额相等, 那么返回 -1.
Example
样例1
输入:
[1, 2, 5]
11
输出: 3
解释: 11 = 5 + 5 + 1
样例2
输入:
[2]
3
输出: -1
import java.util.*;
public class Solution {
/**
* @param coins: a list of integer
* @param amount: a total amount of money amount
* @return: the fewest number of coins that you need to make up
*/
public int coinChange(int[] coins, int amount) {
// write your code here
int[] dp = new int[amount+1];
dp[0] = 0;
for(int i=1;i<=amount;i++)
{
dp[i] = Integer.MAX_VALUE;
for(int j=0;j<coins.length;j++)
{
if(i>=coins[j] && dp[i-coins[j]]!=Integer.MAX_VALUE)
{
dp[i] = Math.min(dp[i-coins[j]]+1,dp[i]);
}
}
}
if(dp[amount] == Integer.MAX_VALUE)
dp[amount] = -1;
return dp[amount];
}
}
例子2
有一个机器人的位于一个 m × n 个网格左上角。
机器人每一时刻只能向下或者向右移动一步。机器人试图达到网格的右下角。
问有多少条不同的路径?
public class Solution {
/**
* @param m: positive integer (1 <= m <= 100)
* @param n: positive integer (1 <= n <= 100)
* @return: An integer
*/
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for(int i=0;i<m;i++)
{
dp[i][0] = 1;
}
for(int i=0;i<n;i++)
{
dp[0][i] = 1;
}
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
dp[i][j] = dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
动态规划总结
四个组成部分
- 确定状态
研究最优决策的最后一步
化为子问题 - 转移方程
根据子问题定义直接得到 - 初始条件和边界情况
细心、考虑周全 - 计算顺序
利用之前的计算结果