https://blog.csdn.net/p10010/article/details/50196211#commentsedit
一,基本概念
动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
二,基本思想与策略
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。
以上都过于理论,还是看看常见的动态规划问题吧!
三,常见动态规划问题
1,找零钱问题
有数组便士,便士中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数的目标(小于等于1000)代表要找到钱数,求换钱有多少种方法。
给定数组便士及它的大小(小于等于50),同时给定一个整数目标,请返回有多少种方法可以凑成目标。
测试样例:
[1 1,2,4],3,3
返回:2
解析:设DP [N] [M]为使用前Ñ中货币凑成的米的种数,那么就会有两种情况:
使用第Ñ种货币:DP [N-1] [M] + DP [N] [M-peney [N]]
不用第Ñ种货币:DP [N-1] [M],为什么不使用第Ñ种货币呢,因为彭尼[N]>米。
这样就可以求出当m> = penney [n]时dp [n] [m] = dp [n-1] [m] + dp [n-1] [m-peney [n]],否则,dp [n] [m] = dp [n-1] [m]
代码如下:
public class Coin {
public int countWays(int[] penny, int n, int aim) {
if(n==0||penny==null||aim<0){
return 0;
}
int[][] pd = new int[n][aim+1];//使用前n种货币
for(int i=0;i<n;i++){
pd[i][0] = 1;
}//做好前面的准备,使用前几种货币组成0的种数都为一
for(int i=1;penny[0]*i<=aim;i++){
pd[0][penny[0]*i] = 1;
}//做好前面的准备,只使用第一种货币形成目标为[penny[0]*i]这些的种数都为一
for(int i=1;i<n;i++){
for(int j=0;j<=aim;j++){
if(j>=penny[i]){
pd[i][j] = pd[i-1][j]+pd[i][j-penny[i]];
}else{
pd[i][j] = pd[i-1][j];
}
}
}
//用于观察记录的每次迭代的记录,其中,pd[n][m]数组的每个元素代表的就是使用前n种货币凑成m的种数
System.out.println("用到的备忘录:");
for(int i=0;i<penny.length;i++){
for(int j=0;j<11;j++){
System.out.print(pd[i][j]);
System.out.print(",");
}
System.out.println("");
}
return pd[n-1][aim];//要的就是最后使用n种货币组成总值为aim的种数
}
public static void main(String[] args)
{
int[] penny={2,3,4};
System.out.println("换硬币的种数:"+new Coin().countWays(penny, 3,10));
}
}
2,走方格问题
有一个矩阵图,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
给定一个矩阵地图及它的行数n和列数m,请返回最小路径和。保证行列数均小于等于100.
测试样例:
[[1,2,3] ],[1,1,1]],2,3-
返回:4
解析:设dp [n] [m]为走到n * m位置的路径长度,那么显而易见dp [n] [m] = min(dp [n-1] [m],dp [n] [m- 1]);
代码如下:
public class MinimumPath {
public int getMin(int[][] map, int n, int m) {
// write code here
int[][] dp = new int[n][m];
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
dp[i][0]+=map[j][0];
}
}
for(int i=1;i<m;i++){
for(int j=0;j<=i;j++){
dp[0][i]+=map[0][j];
}
}
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
dp[i][j] = min(dp[i][j-1]+map[i][j],dp[i-1][j]+map[i][j]);
}
}
//用于观察记录的每次迭代的记录,其中,dp[n][m]
System.out.println("用到的备忘录:");
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
System.out.print(dp[i][j]);
System.out.print(",");
}
System.out.println("");
}
return dp[n-1][m-1];
}
public int min(int a,int b){
if(a>b){
return b;
}else{
return a;
}
}
public static void main(String[] args) {
int[][] arr1 = {{1,1,2}, {2, 3,4}};
int n=2;
int m=3;
System.out.println(new MinimumPath().getMin(arr1, n, m));
}
}
3最长公共序列数
给定两个字符串甲和B,返回两个字符串的最长公共子序列的长度。例如,A = “1A2C3D4B56” ,B =” B1D23CA45B6A”,” 123456 “或者” 12C4B6"都是最长公共子序列。
给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。保证两串长度均小于等于300.
测试样例:
“1A2C3D4B56 ”,10, “B1D23CA45B6A”,12
返回:6
解析:设dp [n] [m],为A的前n个字符与B的前m个字符的公共序列长度,则当A [n] == B [m]的时候,dp [i] [ j] = max(dp [i-1] [j-1] + 1,dp [i-1] [j],dp [i] [j-1]),否则,dp [i] [j] = Math.max(DP [I-1] [j]时,DP [i] [j-1]);
代码如下:
public class LCS {
public int findLCS(String A, int n, String B, int m) {
int[][] dp = new int[n][m];
char[] a = A.toCharArray();
char[] b = B.toCharArray();
for(int i=0;i<n;i++){
if(a[i]==b[0]){
dp[i][0] = 1;
for(int j=i+1;j<n;j++){
dp[j][0] = 1;
}
break;
}
}
for(int i=0;i<m;i++){
if(a[0]==b[i]){
dp[0][i] = 1;
for(int j=i+1;j<m;j++){
dp[0][j] = 1;
}
break;
}
}
for(int i=1;i<n;i++){
for(int j=1;j<m;j++){
if(a[i]==b[j]){
dp[i][j] = max(dp[i-1][j-1]+1,dp[i-1][j],dp[i][j-1]);
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[n-1][m-1];
}
public int max(int a,int b,int c){
int max = a;
if(b>max)
max=b;
if(c>max)
max = c;
return max;
}
public static void main(String[] args) {
String str1="1A2C3D4B56";
String str2="B1D23CA45B6A";
int n=10;
int m=12;
System.out.println(new LCS().findLCS(str1, n,str2, m));
}
}