文章目录
- 一、动态规划入门
- 二、动态规划经典例题
- 2.1 有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。
- 2.2 给定一个矩阵m,从左上角开始,每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中,最小的路径和,,如果给定的路径如下图,则最小的路径和应该为1,3,1,0,6,1,0这条路径,返回最小值为:12![在这里插入图片描述](https://img-blog.csdnimg.cn/20190829134617117.png)
- 2.3 返回数组arr的最长递增子序列
- 2.4 返回两个字符串的最大公共子序列
- 2.5 一个背包有一定的承重 W,有 N 件物品,每件都有自己的价值,记录在数组 v 中,也都有自己的重量,记录在数组 w 中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。
- 2.6 给定两个字符串str1和str2,再给定三个整数ic,dc,rc,分别代表插入、删除、替换一个字符的代价,返回将str1编辑成str2的最小代价。
一、动态规划入门
1.1 array代表数组,数组中的数是正数,代表钱数,每种面值的货币可以使任意张,给定一aim代表要找的钱数,求换钱有多少种方法。比如:arr={5,10,25,1},aim=1000
暴力搜索:一般使用暴力递归
记忆搜索:将每次递归的结果放在hashmap中,下次递归的时候去hash表中查找结果,有,使用记忆的结果,没有则再进行递归。记忆搜索不规定计算的顺序。
经典的动态规划方法:动态规划规定记忆的顺序。
- 如果arr的长度为n,生成列数为aim+1的矩阵dp。dp[i][j]的含义是在使用arr[0,…i]的情况下,组成钱数j有多少种方法。
- 如果完全不用arr[i]货币,只使用arr[0…i-1]货币时,方法数为dp[i-1][j]
- 使用一张arr[i]货币,剩下的钱用arr[0…i-1]货币组成时,方法数为dp[i-1][j-1*arr[i]]
- 使用n张arr[i]货币,剩下的钱用arr[0…i-1]货币组成时,方法数为dp[i-1][j-n*arr[i]]
算法:
- dp[i][j] = dp[i] + dp[i-1][j-arr[i]] + dp[i-1][j-arr[i]*2] + … + dp[i-1][j-array[i]*n];
- n = j / arr[i];
暴力搜索:
public class find {
public static void main(String[] args) {
int[] ints = new int[]{5, 10, 20, 100};
System.out.println(process1(ints, 0,100));
}
public static int process1(int[] arr,int index,int aim)
{
int res =0;
if(index==arr.length - 1)
res = aim==0?1:0;
else
{
for(int i=0;arr[index]*i<=aim;i++)
res += process1(arr,index+1,aim-arr[index]*i);
}
return res;
}
}
记忆搜索:
public class find {
public static void main(String[] args) {
int[] ints = new int[]{5, 10, 20, 100};
System.out.println(coins1(ints,100));
}
public static int coins1(int[] arr,int aim)
{
int[][] map = new int[arr.length][aim + 1];
if(arr==null||arr.length==0||aim<0)
return 0;
return process1(arr,0,aim,map);
}
public static int process1(int[] arr,int index,int aim, int[][] map) {
int res = 0;
int mapValue = 0;
if (index == arr.length - 1) {
res = aim == 0 ? 1 : 0;
} else {
for (int i = 0; arr[index] * i <= aim; i++) {
mapValue = map[index + 1][aim - arr[index] * i];
if(mapValue != 0){
res += mapValue;
}else {
res += process1(arr, index + 1, aim - arr[index] * i, map);
}
}
}
return res;
}
}
参考:动态规划算法案例分析
- 动态规划:
public class find {
public static void main(String[] args) {
int[] ints = new int[]{5, 10, 20, 100};
System.out.println(getCount(ints,3,100));
}
public static int getCount(int[] penny,int n, int aim) {
// write code here
int[][] dp = new int[n][aim+1];
//矩阵初始化
for(int i=0;i<n;i++)
{
for(int j=0;j<aim+1;j++)
{
dp[i][j]=0;
}
}
//初始化第一行数值
for(int j=0;j<aim+1;j++)
{
if(j%penny[0]==0)
dp[0][j]=1;
}
//初始化第一列数值
for(int i=0;i<n;i++)
{
dp[i][0]=1;
}
//求其他行数值
for(int i=1;i<n;i++)
{
for(int j=1;j<aim+1;j++)
{
for(int k=0;(j-k*penny[i])>=0;k++)
{
//根据上面分析的公式
dp[i][j] += dp[i-1][j-k*penny[i]];
}
}
}
return dp[n-1][aim];
}
}
二、动态规划经典例题
2.1 有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。
暴力搜索:
public class literator {
public static void main(String[] args) {
System.out.println(method(5));
}
public static int method(int i){
if(i == 0){
return 0;
}
if(i == 1){
return 1;
}
if(i == 2){
return 2;
}
else {
return method(i - 1) + method(i - 2);
}
}
}
2.2 给定一个矩阵m,从左上角开始,每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中,最小的路径和,,如果给定的路径如下图,则最小的路径和应该为1,3,1,0,6,1,0这条路径,返回最小值为:12
- 建立一个二维数组array,array[i][j]代表从0,0出发到array[i][j]的最小值
- 则到达array[i][j]最小值的情况是从上面或者从左边来的最小值加上array[i][j]的值构成的。
public class literator {
public static void main(String[] args) {
int[][] array = new int[][]{{1,3,5,9},{8,1,3,4},{5,0,6,1},{8,8,4,0}};
System.out.println(method(array, 3, 3));
}
public static int method(int[][] array, int row, int cow){
int[][] dp = new int[array.length][array[1].length];
dp[0][0] = array[0][0];
for (int i = 1; i < array[1].length; i++) {
dp[0][i] = array[0][i] + array[0][i-1];
}
for (int i = 1; i < array.length; i++) {
dp[i][0] = array[i][0] + array[i-1][0];
}
for (int i = 1; i < array.length; i++) {
for (int i1 = 1; i1 < array[1].length; i1++) {
dp[i][i1] = dp[i-1][i1] < dp[i][i1-1] ? dp[i-1][i1] + array[i][i1]:dp[i][i1-1] + array[i][i1];
}
}
return dp[row][cow];
}
}
2.3 返回数组arr的最长递增子序列
思路
- 原数组int[] array
- 建立一个数组int[] test,test[i]表示以array[i]为结尾的最大子序列长度。
- test[i]的求法是比array[i]小的最大的的test[j]的值加1。
注意:是以array[i]为结尾的最大子序列长度,那就是比他小的array[j]中选出最大的test[j]值再加1。
public class literator {
public static void main(String[] args) {
int[]array = new int[]{2,1,3,42,45,2,6,7,2,2,3,4,5,6};
System.out.println(method(array));
}
public static int method(int[] array){
int[] compare = new int[array.length];
compare[0] = 1;
for (int i = 1; i < compare.length; i++) {
int flag = 0;
for (int j = 0; j < i; j++) {
if(array[j] < array[i] && compare[j] > flag){
flag = compare[j];
}
}
compare[i] = flag + 1;
}
int flag = 0;
for (int i = 0; i < compare.length; i++) {
if(flag < compare[i]){
flag = compare[i];
}
}
return flag;
}
}
2.4 返回两个字符串的最大公共子序列
- 字符串一长度为m
- 字符串二长度为n
- 生成m*n的二维数组,int[][] array,array[i][j]表示子符串m的第(i+1)个字符,符串m的第(j+1)个字符为结尾的最大公共子序列长度。
思路:
- array[i][j]有三种来源:
array[i-1][j]
array[i][j-1]
array[i-1][j-1],如果此时(str1.charAt(i) == str2.charAt(j))为true,则flag = array[i-1][j-1] + 1,array[i][j]是上述三种来源最大的值。
public class literator {
public static void main(String[] args) {
String str1 = "abc123def";
String str2 = "123abcdef";
System.out.println(method(str1, str2));
}
public static int method(String str1, String str2){
int n = str1.length();
int m = str2.length();
int[][] array = new int[n][m];
if(str1.charAt(0) == str2.charAt(0)){
array[0][0] = 1;
}else {
array[0][0] = 0;
}
for (int i = 1; i < n; i++) {
if(str2.charAt(0) == str1.charAt(i) || array[i-1][0] == 1)
array[i][0] = 1;
}
for (int i = 1; i < m; i++) {
if(str1.charAt(0) == str2.charAt(i) || array[0][i-1] == 1)
array[0][i] = 1;
}
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
int flag = array[i-1][j];
if(array[i][j-1] > flag){
flag = array[i][j-1];
}
int flag2 = 0;
if(str1.charAt(i) == str2.charAt(j)) {
flag2 = array[i - 1][j - 1] + 1;
}else {
flag2 = array[i - 1][j - 1];
}
if(flag2 > flag){
array[i][j] = flag2;
}else {
array[i][j] = flag;
}
}
}
return array[n - 1][m - 1];
}
}
2.5 一个背包有一定的承重 W,有 N 件物品,每件都有自己的价值,记录在数组 v 中,也都有自己的重量,记录在数组 w 中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。
dp[x][y]代表前x件物品,不超过重量y的时候的最大价值
思路:第x件物品的情况:
1.拿第x件物品,则前x-1件物品的重量不超过:y-w[x]
2.不拿第x件物品,则前x-1件物品的重量不超过y
dp[x][y] = {dp[x-1][y-w[x]] + v[x],dp[x-1][y]}中的最大值
public class literator {
public static void main(String[] args) {
int[] value = {0, 10, 80, 30, 35};
int[] weight = {0, 40, 10, 20, 30};
System.out.println(method(value, weight, 80));
}
public static int method(int[] value, int weight[], int aim) {
int row = value.length;
int[][] dp = new int[row][aim + 1];
for (int i = 1; i < row; i++) {
for (int j = 1; j < aim + 1; j++) {
dp[i][j] = dp[i - 1][j];
if (j >= weight[i]) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
}
return dp[row-1][aim];
}
}
2.6 给定两个字符串str1和str2,再给定三个整数ic,dc,rc,分别代表插入、删除、替换一个字符的代价,返回将str1编辑成str2的最小代价。
举例:
str1=“abc” str2=“adc” ic=5 dc=3 rc=2,从"abc"编辑到"adc"把b替换成d代价最小,为2;
str1=“abc” str2=“adc” ic=5 dc=3 rc=10,从"abc"编辑到"adc",先删除b再插入d代价最小,为8;
思路:
- dp[i][j]代表从长度为i的字符串变换到长度为j的字符串的最下代价
- 最小代价的四种来源:
替换,插入,删除,不做操作这4中操作的最小值。
package com.example.demo;
public class literator {
public static void main(String[] args) {
String str1 = "abd";
String str2 = "abc";
System.out.println(method(str1, str2, 5, 6, 100));
}
public static int method(String str1, String str2,int ic, int dc, int rc) {
int row = str1.length();
int line = str2.length();
int[][] array = new int[row + 1][line + 1];
array[0][0] = 0;
for (int i = 1; i < row + 1; i++) {
array[i][0] = i * dc;
}
for (int i = 1; i < line + 1; i++) {
array[0][i] = i * ic;
}
for (int i = 1; i < row + 1; i++) {
for (int j = 1; j < line + 1; j++) {
int flag = array[i-1][j] + dc;
if(flag > array[i][j-1] + ic){
flag = array[i][j-1] + ic;
}
if(str1.charAt(i-1) != str2.charAt(j-1)) {
if(flag > array[i - 1][j - 1] + rc){
flag = array[i - 1][j - 1] + rc;
}
}else {
if(flag > array[i - 1][j - 1]){
flag = array[i - 1][j - 1];
}
}
array[i][j] = flag;
}
}
return array[row][line];
}
}