文章目录
什么是动态规划
动态规划题目特点(常见)
1.计数
有多少种方法走到右下角
有多少种方法选出k个数使得和是sum
2.求最大最小值
从左上角走到右下角路径的最大数字和
最长上升子序列长度
3.求存在性
取石子游戏,先手是否必胜
能不能选出k个数使得和是sum
例题
最值型:Coin Change
Description
You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
Input
输入一组数据,输入第一个数字n,代表硬币的种类个数,接下来有n个硬币的面值,最后输入要凑齐的钱
Output
输出用最少的硬币付钱的个数,如果这些硬币的任何组合都不能凑齐这笔钱,则返回-1
Sample Input
3
2 5 7
27
3
1 2 5
11
1
2
3
Sample Output
5
3
-1
解题步骤
一:确定状态
-
状态在动态规划中的作用属于定海神针
-
简单的说,解动态规划的时候需要开一个数组,数组的每个元素f[i]或者f[i] [j]代表什么
–类似于解数学题中,X,Y,Z代表什么 -
确定状态需要两个意识︰
-最后一步
-子问题
最后一步
子问题
-
所以我们就要求︰最少用多少枚硬币可以拼出27- ak,原问题是最少用多少枚硬币拼出27
-
我们将原问题转化成了一个子问题,而且规模更小:27- ak为了简化定义,我们设状态f(X)=最少用多少枚硬币拼出X
-
等等,我们还不知道最后那枚硬币ak是多少。最后那枚硬币ak只可能是2,5或者7
-
如果ak是2,f(27)应该是f(27-2)+1(加上最后这一枚硬币2)
-
如果ak是5,f(27)应该是f(27-5)+1(加上最后这一枚硬币5)
-
如果ak是7,f(27)应该是f(27-7)+1(加上最后这一枚硬币7)
-
除此以外,没有其他的可能了
-
需要求最少的硬币数,所以∶
f(27)= min{f(27-2)+1, f(27-5)+1, f(27-7)+1}
递归解法
问题是会出现重复计算,时间复杂度高,效率低下
二:转移方程
三:初始状态和边界情况
- 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[-1]=f[-2]=…=正无穷 - 所以f[1] =min{f[-1]+1,f[-4]+1,f[-6]+1}=正无穷,表示拼不出来1
- 初始条件:f[0]= 0
四:计算顺序
- 拼出X所需要的最少硬币数:f[X]= min{f[X-2]+1, f[X-5]+1, f[X-7]+1}·初始条件:f[0] =0
- 然后计算f[1], f[2],… f[27]
- 当我们计算到f[X]时,f[X-2], f[X-5],f[X-7]都已经得到结果了
小结
-
求最值型动态规划·动态规划组成部分∶
1.确定状态-
最后一步(最优策略中使用的最后一枚硬币ak )
-
化成子问题(最少的硬币拼出更小的面值27-ak )
2.转移方程
- [X]=min{f[X-2]+1,f[X-5]+1,f[X-7]+1}
3.初始条件和边界情况
- f[0] =0,如果不能拼出Y,f[Y]=正无穷
4.计算顺序
- f[0],f[1],f[2],…
-
-
消除冗余,加速计算
代码实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] coin = new int[n];
for(int i=0;i<n;i++) {
coin[i] = in.nextInt();
}
int money = in.nextInt();
System.out.println(fun(money,coin));
in.close();
}
public static int fun(int m,int[] coin) {
int[] f = new int[m+1];//记录0~m元分别要用的最少的硬币数
int i,j;
f[0] = 0;
for(i=1;i<=m;i++) {
f[i] = Integer.MAX_VALUE;
for(j=0;j<coin.length;j++) {
if(i-coin[j]>=0 && f[i-coin[j]]!=Integer.MAX_VALUE) //数组的下标不能为负数&无穷大无法计算
f[i] = Math.min(f[i], f[i-coin[j]]+1);
}
}
if(f[m]==Integer.MAX_VALUE)//如果无法这些硬币无法凑成需要的钱数,就返回-1
f[m] = -1;
return f[m];
}
}
计数型:Unique Path
Description
A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
How many possible unique paths are there?
Above is a 7 x 3 grid. How many possible unique paths are there?
Note: m and n will be at most 100.
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
- Right -> Right -> Down
- Right -> Down -> Right
- Down -> Right -> Right
Input
输入m,n,分别是行和列
Output
输出机器人有多少种不同的路径/走法走到右下角
Sample Input
3 2
7 3
Sample Output
3
28
解题步骤
一:确定状态
最后一步
无论机器人用何种方式到达右下角,总有最后挪动的一步:
向右或者向下
右下角坐标设为(m-1, n-1)
那么前一步机器人一定是在(m-2, n-1)或者(m-1, n-2)
子问题
如果机器人有X种方式从左上角走到(m-2,n-1),有Y种方式从左上角走到(m-1,n-2),则机器人有X+Y种方式走到(m-1, n-1)
问题转化为,机器人有多少种方式从左上角走到(m-2, n-1)和(m-1, n-2)
原题要求有多少种方式从左上角走到(m-1, n-1)
状态:设f [i][j]为机器人有多少种方式从左上角走到(i, j)
为什么是X+Y?
这就涉及到了加法原理
加法原理是分类计数原理,常用于排列组合中,具体是指:做一件事情,完成它有n类方式,第一类方式有M1种方法,第二类方式有M2种方法,……,第n类方式有Mn种方法,那么完成这件事情共有M1+M2+……+Mn种方法。
比如说:从武汉到上海有乘火车、飞机、轮船3种交通方式可供选择,而火车、飞机、轮船分别有k1,k2,k3个班次,那么从武汉到上海共有
k1+k2+k3种方式可以到达。
二:转移方程
三:初始条件和边界情况
初始条件:f[0][0]=1,因为机器人只有一种方式到左上角
边界情况:i=0或j=0,则前一步只能有一个方向过来→f[i][j]= 1
四:计算顺序
f[0][0]=1
计算第0行:f[0][0], f[0][1],…f[0][n-1]·计算第1行:f[1][0], f[1][1],…, f[1][n-1]
…
计算第m-1行:f[m-1][0], f[m-1][1],… f[m-1][n-1]
答案是f[m-1][n-1]
时间复杂度(计算步数): O (MN)
空间复杂度(数组大小) : O(MN)
代码实现
import java.util.Scanner;
public class UniquePath {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in = new Scanner(System.in);
int m = in.nextInt();
int n = in.nextInt();
int[][] t = new int[m][n];
for(int i=0;i<m;i++) {
for(int j=0;j<n;j++) {
if(i==0 || j==0)
t[i][j] = 1;
else
t[i][j] = t[i-1][j] + t[i][j-1];
}
}
System.out.println(t[m-1][n-1]);
in.close();
}
}
存在型:Jump Game
Given an array of non-negative integers, you are initially positioned at the first index of the array.
Each element in the array represents your maximum jump length at that position.
Determine if you are able to reach the last index.
Example 1:
Input: [2,3,1,1,4]
Output: true
Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.
Example 2:
Input: [3,2,1,0,4]
Output: false
Explanation: You will always arrive at index 3 no matter what. Its maximum
jump length is 0, which makes it impossible to reach the last index.
确定状态
- 最后一步∶如果青蛙能跳到最后一块石头n-1,我们考虑它跳的最后一步
这一步是从石头i跳过来,i<n-1 - 这需要两个条件同时满足︰
青蛙可以跳到石头i
最后一步不超过跳跃的最大距离:n-1-i<=a;
子问题
那么,我们需要知道青蛙能不能跳到石头i (i<n-1),而我们原来要求青蛙能不能跳到石头n-1
状态︰设f[i]表示青蛙能不能跳到石头j
转移方程
初始状态
·设f[j]表示青蛙能不能跳到石头j
·初始条件:f[0] =True,因为青蛙一开始就在石头0
计算顺序
设f[j]表示青蛙能不能跳到石头j
初始化f[0]=True
计算f[1],f[2],… f[n-1]
答案是f[n-1]
时间复杂度:O(N2)
空间复杂度(数组大小) : O(N)
代码实现
import java.util.Scanner;
public class Mian {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in = new Scanner(System.in);
int n = 5;
int[] t = new int[n];
boolean[] f = new boolean[n];
for(int i=0;i<n;i++) {
t[i] = in.nextInt();
}
f[0] = true;
//从i石头跳到j石头
for(int j=1;j<n;j++) {
f[j] = false;
for(int i=0;i<j;i++) {
if(f[i] && i+t[i]>=j) {//所以题目的意思不是要刚好跳到j石头,只要跳到或跳过就行
f[j] = true;
break;
}
}
}
System.out.println(f[n-1]);
in.close();
}
}
详细:[LeetCode]55. Jump Game 跳跃游戏
总结
确定状态
研究最优策略的最后一步化为子问题
转移方程
根据子问题定义直接得到
初始条件和边界情况
细心,考虑周全
计算顺序
利用之前的计算结果