动态规划:
动态规划(Dynamic Programming,简称DP),需要分解出问题的子结构以及通过子结构重新构造最优解。动态规划不像回溯法,有套路可以套用,动态规划需要大量练习,才能掌握规律。
一般思路:
1.判断问题的子结构,有最优子结构时,可用动态规划!!(即从子问题的最优解,就能拼接出整体的最优解!!)
2.求解重叠子问题。一个递归算法不断地调用同一问题(子问题大量重复,可以存储,减少计算),递归可以转化为查表从而利用子问题的解。(注意与分治法区别开,分治法每次递归都产生新的问题)
特殊思路:备忘录法
备忘录法是动态规划的一种变形,最开始随机填一个数字,遇到子问题时,计算并填表。
实例可参照矩阵链乘法。
常见题目:
1. 硬币找零:假设有1,3,5三种硬币,数量无限,问当总额为某个值时需要最少硬币数为多少。
2. 装配线调度:某个工厂有两条装配线,汽车底盘经过每个流程时都有相应的时间消耗,问怎么安排,可以让进入流水线到出厂时,总时间最少。
3. 字符串相似度/编辑距离(edit distance)
有两个不同的字符串,只对一个字符串进行“增删改”操作,每次变化只有一个字符,问变成另一个字符串时,最少操作几次?
4.最长公共子序列(Longest Common Subsequence,LCS)
对于序列S和T,求它们的最长公共子序列。例如X={A,B,C,B,D,A,B},Y={B,D,C,A,B,A}则它们的LCS是{B,C,B,A}和{B,D,A,B}。求出一个即可。
5.铺瓷砖问题(剑指offer,编程之美原题)
用 1 * 2 的瓷砖覆盖 n * m 的地板,问共有多少种覆盖方式?
答:可以转换为斐波那契数列题。f(n)=f(n-1)+f(n-2);
6. 0-1背包
题目背景如下:有一个背包,容量有限。还有好几样物品,体积和价值各不相同,如何尽量装最高价值的物品呢?用实际数据举例如下:
* 物品编号: 1 2 3 4
* 物品体积: 2 3 4 5
* 物品价值: 3 4 5 6
即前i件物品,容积为j时,最大价值为dp[i][j],用图表法计算如下:
有3种可能。
总容量连当前物品都放不下,那么当前物品肯定不放。前i个物品总体组合与前i-1一致。那么就把问题抛给前i-1了
总容量可以放下当前这一个物品。那么,给当前物品留有相应空间下,剩下的空间内,前i-1个物品总价值加当前的物品价值的组合。
总容量放得下当前物品,但还有一种可能性,我不放当前的,那么照顾当前的物品,还不如之前的i-1个物品呢。
实际举例如下:
第一种情况。如dp[3][3],当前物品体积是4,但是总容量只有3,根本放不下,那么dp[3][3],只能和dp[2][3]一致,都是4.
第二种情况,放得下。如dp[3][4],此时第3个体积为4,容量为4,总容量恰好能放下当前的。如果放当前的,那么留给之前的体积仅为0,一个都不放。第3个价值为5,所以总共价值为5。
如果不放该物品,那么dp[i][j]=dp[i-1][j]=4。它俩取最大值。当然是5.所以dp[3][4]=5.
总结:
注意:当前装得下时,指的是总容量装得下!!!要不装这个最大的,把问题抛给前面。要不不装大的,直接用前面的。这个思路和青蛙跳楼梯是一样的!!!!
参考文献:https://www.cnblogs.com/jiyongjia/p/13475026.html
import java.util.ArrayList;
/**
* Date: 2020/11/11 11:36
* Description:
* <p>
* 问题:现有背包。其中有四个商品。价值体积如下
* 物品编号: 1 2 3 4
* 物品体积: 2 3 4 5
* 物品价值: 3 4 5 6
* 问:如何才能保证在背包容量为8的情况下装的价值最大?
* <p>
* 步骤1:填表之后的结果:
* 0 0 0 0 0 0 0 0 0
* 0 0 3 3 3 3 3 3 3
* 0 0 3 4 4 7 7 7 7
* 0 0 3 4 5 7 8 9 9
* 0 0 3 4 5 7 8 9 10
* 步骤2:回溯
*
* @author zf
*/
public class beibao {
public static void main(String[] args) {
int[] size = new int[]{0, 2, 3, 4, 5}; //第0位放一个默认的0值,为了下边方便取size[i]就为i号物品的体积
int[] money = {0, 3, 4, 5, 6}; //第0位放一个默认的0值,为了下边方便取money[i]就为i号物品的价值
int target = 8; //指定的背包大小
//调用
int[][] ints = dpWrite(size, money, target);
ArrayList<Integer> huisu = huisu(ints, size, target);
System.out.println("您输入的背包大小是:" + target);
System.out.println("添加的物品编号是:" + huisu);
System.out.println("最大价值:" + ints[size.length - 1][target]);
}
public static int[][] dpWrite(int[] size, int[] money, int targetValue) {
//这里构造一个例如4*8的dp,行代表截止到i背包的最优组合的价值,j代表的背包的容量值
int[][] dp = new int[size.length][targetValue + 1];
//初始化第0行的值
for (int j = 0; j <= targetValue; j++) {
dp[0][j] = 0;
}
//初始化第一列的值
for (int i = 1; i < size.length; i++) {
dp[i][0] = 0;
//dp[i][1] = 0;
}
//初始化中间值i
for (int i = 1; i < size.length; i++) {
for (int j = 1; j <= targetValue; j++) {
//如果第i号背包的体积小于当前的j背包容量,就保持和dp[i-1]值相同,即没放入
if (size[i] > j) {
dp[i][j] = dp[i - 1][j];
} else {
/*即如果当前i号物品体积可以放入的话,就看预留该体积后剩余的价值在i-1号物品中的最大值加上该物品的价值和是否
是大于dp[i-1][j]的值,谁大取谁*/
dp[i][j] = Math.max(dp[i - 1][j - size[i] > 0 ? j - size[i] : 0] + money[i], dp[i - 1][j]);
}
}
}
//这样就填表完成了。剩下的就是需要回溯取出看对应某价值target的物品组合是什么
//先打印一下填的表
/*for (int i = 0; i < dp.length; i++) {
for (int j = 0; j < dp[i].length; j++) {
System.out.print(dp[i][j]+"\t");
}
System.out.println();
}*/
return dp;
}
public static ArrayList<Integer> huisu(int[][] dp, int[] size, int t) {
ArrayList<Integer> res = new ArrayList<>();
int i = dp.length - 1;
int j = t;
while (i > 0 || j > 0) {
if (dp[i][j] == dp[i - 1][j]) {
i--;
} else {
res.add(i);
j = j - size[i];
i--;
}
}
return res;
}
}
7. 有代价的最短路径
青蛙跳台阶问题。(这个和硬币找零很像)
8.M*N的矩阵最短路径问题
有一个M*N的矩阵,每个网格都有一个值,求起点grid[0][0]到grid[M-1][N-1]的值的最小累加和。
这个问题很简单,构造一个相同大小的矩阵dp[M-1][N-1],首先计算第一行和第一列的值,然后其余值都为dp[i][j]=grid[i][j]+Math.min(dp[i-1][j],dp[i][j-1]),最后返回dp[M-1][N-1]即可。
public static int minPathSum(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int rows = grid.length,cols = grid[0].length;
int[][] dp = new int[rows][cols];
dp[0][0] = grid[0][0];
//第一列的初始值
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
//第一行的值
for (int j = 1;j < cols;j++){
dp[0][j] = grid[0][j] + dp[0][j-1];
}
//填写其他地方的值
for (int i = 1;i<rows;i++){
for (int j = 1;j<cols;j++){
dp[i][j] = grid[i][j] + Math.min(dp[i-1][j] , dp[i][j-1]);
}
}
return dp[rows -1][cols -1];
}