五、常见动态规划问题
1、找零钱问题
有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。
给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。
测试样例:
[1,2,4],3,3
返回:2
解析:设dp[n][m]为使用前n中货币凑成的m的种数,那么就会有两种情况:
使用第n种货币:dp[n-1][m]+dp[n-1][m-peney[n]]
不用第n种货币:dp[n-1][m],为什么不使用第n种货币呢,因为penney[n]>m。
这样就可以求出当m>=penney[n]时 dp[n][m] = dp[n-1][m]+dp[n][m-peney[n]],否则,dp[n][m] = dp[n-1][m]
代码如下:
[java] view plain copy
import java.util.*;
public class Exchange {
public int countWays(int[] penny, int n, int aim) {
// write code here
if(n0||pennynull||aim<0){
return 0;
}
int[][] pd = new int[n][aim+1];
for(int i=0;i<n;i++){
pd[i][0] = 1;
}
for(int i=1;penny[0]*i<=aim;i++){
pd[0][penny[0]*i] = 1;
}
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];
}
}
}
return pd[n-1][aim];
}
2、走方格问题
有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
给定一个矩阵map及它的行数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]);
代码如下:
[java] view plain copy
import java.util.*;
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=0;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]);
}
}
return dp[n-1][m-1];
}
public int min(int a,int b){
if(a>b){
return b;
}else{
return a;
}
}
3、走台阶问题
有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。为了防止溢出,请将结果Mod 1000000007
给定一个正整数int n,请返回一个数,代表上楼的方式数。保证n小于等于100000。
测试样例:
1
返回:1
解析:这是一个非常经典的为题,设f(n)为上n级台阶的方法,要上到n级台阶的最后一步有两种方式:从n-1级台阶走一步;从n-1级台阶走两步,于是就有了这个公式f(n) = f(n-1)+f(n-2);
代码如下:
[java] view plain copy
import java.util.*;
public class GoUpstairs {
public int countWays(int n) {
// write code here
if(n<=2)
return n;
int f = 1%1000000007;
int s = 2%1000000007;
int t = 0;
for(int i=3;i<=n;i++){
t = (f+s)%1000000007;
f = s;
s = t;
}
return t;
}
}
4、最长公共序列数
给定两个字符串A和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]);
代码如下:
[java] view plain copy
import java.util.*;
public class LCS {
public int findLCS(String A, int n, String B, int m) {
// write code here
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;
}
}