动态规划算法通常基于一个递推公式及一个或多个初始状态。当前子问题的解将由上一次子问题的解推出。使用动态规划来解题只需要多项式时间复杂度,因此它比回溯法、暴力法等要快许多。
解决动态规划问题的关键是要找到状态转移方程。将问题分解成最小的子问题,找到由子问题到全局问题的解决方案。
可以采用动态规划求解的问题的一般要具有3个性质:
(1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。
求解动态规划问题的基本步骤:
(1)分析最优解的性质,并刻画其结构特征。
(2)递归的定义最优解。
(3)以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值
(4)根据计算最优值时得到的信息,构造问题的最优解
例1:有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。
在这个问题上,我们让f(n)表示走上n级台阶的方法数。那么当n为1时,f(n) = 1,n为2时,f(n) =2,就是说当台阶只有一级的时候,方法数是一种,台阶有两级的时候,方法数为2。那么当我们要走上n级台阶,必然是从n-1级台阶迈一步或者是从n-2级台阶迈两步,所以到达n级台阶的方法数必然是到达n-1级台阶的方法数加上到达n-2级台阶的方法数之和。即f(n) = f(n-1)+f(n-2),我们用dp[n]来表示动态规划表,dp[i],i>0,i<=n,表示到达i级台阶的方法数。
public class steps {
static int[] array;
public static int step(int n){
if(n<=2)
return n;
int f = 1%2147483647;
int s = 2%2147483647;
int t = 0;
for(int i=3;i<=n;i++){
t = (f+s)%2147483647;
f = s;
s = t;
}
return t;
}
public static void main(String[] args) {
System.out.println(step(5));
}
}
例2:有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,
路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
我们必须注意到的一点是,到达一个格子的方式最多只有两种:从左边来的(除了第一列)和从上边来的(除了第一行)。因此为了求出到达当前格子的最短路径,我们就要先去考察那些能到达当前这个格子的格子,到达它们的最短路径。经过上面的分析,很容易可以得出问题的状态和状态转移方程。状态S[i][j]表示我们走到(i, j)这个格子时,最短的路径。那么,状态转移方程如下:
S[i][j]=A[i][j] + min(S[i-1][j], if i>0 ; S[i][j-1], if j>0)
其中i代表行,j代表列,下标均从0开始。
public class MinPath {
public static int getMin(int[][] map, int n, int m) {
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 static int min(int a,int b){
if(a>b){
return b;
}else{
return a;
}
}
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
int m=sc.nextInt();
int n=sc.nextInt();
int map[][]=new int[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
map[i][j]=sc.nextInt();
}
} System.out.println(getMin(map,m,n));
}
例3:给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。
设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] =max(dp[i-1][j],dp[i][j-1]);
public class LCS {
public static 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 static 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) { System.out.println(findLCS("1A3C3D4B56",10,"B1D23CA45B6A",12));
}
}