动态规划的难点就在于找出子问题之间的联系,可以先算出几种比较简单的情况对应问题的解,再用数学表示式列出前后两种情况之间的关系,即可得到问题的解。
一、走方格问题
1.问题引入:
有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。给定一个矩阵map及它的行数n和列数m,请返回最小路径和。保证行列数均小于等于100.
输入输出样例:
第一行,输入n,m表示这个矩阵的行数和列数,接下来的n行,输入每行的m个数字,也即每个格子的权值。最后输出最小路径和。如:
2 3
1 2 3
1 1 1
输出:4
2.思路分析:
因为只能向右和向下,所以从第一个格子到某个格子(不在第一排也不在第一列,否则没有编程的必要了)的最短距离只与第一个格子到它左边格子的最短距离以及第一个格子到它上面格子的最短距离这两个值有关,取这两个值中的较小者。可以设dp[n][m]为走到n*m位置的最短路径长度,则dp[n][m] = Min(dp[n-1][m],dp[n][m-1]),这就运用到了动态规划的思想。题目要求算出复杂情况的值,而动态规划则是算出几个简单的作为已知值,然后找规律由后往前推理。
3.代码如下:
<span style="color:#330099">package 动态规划;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class 走方格问题 {
public static void main(String args[]) throws IOException{
BufferedReader buf=new BufferedReader(new InputStreamReader(System.in));
String str1=buf.readLine();
String str[]=str1.split(" ");
int n=Integer.parseInt(str[0]); //行数
int m=Integer.parseInt(str[1]); //列数
int map[][]=new int[n][m];
for(int i=0;i<n;i++){
String s1=buf.readLine();
String s2[]=s1.split(" ");
for(int j=0;j<m;j++){
map[i][j]=Integer.parseInt(s2[j]);
}
}
int min=getMin(map,n,m);
System.out.print(min);
}
public static int getMin(int[][] map, int n, int m) {
//动态规划需要先算出前面几个值,作为已知值,之后才能由后向前推
int[][] dp = new int[n][m];
for(int i=0;i<n;i++){
//竖着从map[0][0]到map[i][0]的路径长,最简单的情况之一,作为已知值
for(int j=0;j<=i;j++){
dp[i][0]+=map[j][0];
}
}
for(int i=0;i<m;i++){
//横着从map[0][0]到map[0][i]的路径长,最简单的情况之二,作为已知值
for(int j=0;j<=i;j++){
dp[0][i]+=map[0][j];
}
}
for(int i=1;i<n;i++){
//由后往前推
//可比性,最少应有一个右,一个下,所以从map[1][1]开始
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;
}
}
}</span>
三、硬币凑数问题
1.问题引入:
假设现在有面值为1元、3元和5元的硬币若干枚,如何用最少的硬币凑够需要的钱数。现输入一个数,表示需要的钱数,要求输出一个整数表示最小的硬币数。
2.思路分析:
动态规划算法广泛地用于最优化问题中,在我们从 1 元开始依次找零时,可以尝试一下当前要找零的面值(这里指 1 元)是否能够被分解成另一个已求解的面值的找零需要的硬币个数再加上这一堆硬币中的某个面值之和,如果这样分解之后最终的硬币数是最少的,那么问题就得到答案了。这就是动态规划的思想。
3.代码如下:
<span style="color:#330099">package 动态规划;
import java.util.Scanner;
public class 硬币凑数 {
public static void main(String[] args) {
int kinds[]={5,3,1};
int n=new Scanner(System.in).nextInt();
fun(kinds,kinds.length,n);
}
public static void fun(int kinds[],int len,int money){
int min_money[]=new int[money+1]; //下标表示钱数money,min_money[money]表示money对应的最小数量
min_money[1]=0; //第一个需要初始化
for(int i=1;i<money+1;i++){
int min=money;
for(int j=0;j<len;j++){
if(kinds[j]<=i){
int t=min_money[i-kinds[j]]+1;
min=min(min,t);
}
}
min_money[i]=min;
System.out.println(i+"最小需要"+min_money[i]);
}
}
public static int min(int m,int n){
if(m>n) return n;
else return m;
}
}</span>
四、K好数
1.问题描述
如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数。求L位K进制数中K好数的数目。例如K = 4,L = 2的时候,所有K好数为11、13、20、22、30、31、33 共7个。由于这个数目很大,请你输出它对1000000007取模后的值。
输入包含两个正整数,K和L。
对于30%的数据,KL <= 106;
对于50%的数据,K <= 16, L <= 10;
对于100%的数据,1 <= K,L <= 100。
2.思路:
第i位数放置j所得到的所有K好数由i-1进制数的所有K好数之和去除与j相邻的两种情况求得。
3.代码如下:
<span style="color:#330099">package 动态规划;
import java.util.Scanner;
public class K好数 {
public static void main(String args[]){
int k,l,i,j,t;
long d[][]=new long[105][105];
int N=1000000007;
long sum;
Scanner sc=new Scanner(System.in);
k=sc.nextInt();
l =sc.nextInt();
for(j=0;j<k;j++)
d[1][j]=1;
for(i=2;i<=l;i++)
for(j=0;j<k;j++)
for(t=0;t<k;t++)
if(t!=j-1&&t!=j+1)
{
d[i][j]+=d[i-1][t];
d[i][j]%=N;
}
sum=0;
for(j=1;j<k;j++)
{
sum+=d[l][j];
sum%=N;
}
System.out.println(sum);
}
}</span>
五、走台阶问题
给定一个正整数int n,请返回一个数,代表上楼的方式数。保证n小于等于100000。
测试样例:
2
返回:2
设f(n)为上n级台阶的方法,要上到n级台阶的最后一步有两种方式:从n-1级台阶走一步;从n-2级台阶走两步,则有状态转移公式f(n) = f(n-1)+f(n-2);
package 动态规划;
import java.util.Scanner;
public class 走台阶问题 {
public static void main(String args[]){
int n=new Scanner(System.in).nextInt();
int f=fun(n);
System.out.print(f);
}
public static int fun(int n){
if(n<=2){
return n;
}else{
int f1=fun(1);
int f2=fun(2);
int f=0;
for(int i=3;i<=n;i++){
f=f1+f2;
f1=f2;
f2=f;
}
return f;
}
}
}
六、有向五环图最短路径问题
1.问题引入:

2.思路分析:


3.代码如下:
<span style="color:#330099">package 动态规划;
public class 有向五环图最短路径 {
private static int x=9999;
private static int max=9999;
private static int dist[]=new int[10];//记录原点到某点最短路径为多少
//private static int path[]=new int[10];//记录最短路径
//private static int data[][]=new int[10][10];
public static void main(String args[]){
int i,m;
int a[][]={ //从i到j可直达的路径长度,不可直达的设为无穷大(x)
{x,4,2,3,x,x,x,x,x,x},
{x,x,x,x,10,9,x,x,x,x},
{x,x,x,x,6,7,10,x,x,x},
{x,x,x,x,x,3,8,x,x,x},
{x,x,x,x,x,x,x,4,8,x},
{x,x,x,x,x,x,x,9,6,x},
{x,x,x,x,x,x,x,5,4,x},
{x,x,x,x,x,x,x,x,x,8},
{x,x,x,x,x,x,x,x,x,4},
{x,x,x,x,x,x,x,x,x,x}};
fpath(a);
System.out.print("最短路径大小为:"+dist[9]);
/* m=froute(a);
for(i=m-1;i>=0;i--)
System.out.println("最短路径为:"+path[i]);*/
}
public static void fpath(int a[][])
{
int i,j,k;
dist[0]=0;
for(i=1;i<10;i++)
{
k=max;
for(j=0;j<i;j++)
{
if(a[j][i]!=x)
if((dist[j]+a[j][i])<k)
k=dist[j]+a[j][i];
}
dist[i]=k;
}
}
/*public static int froute(int a[][])
{
int j,b,k=1,i=9;
path[0]=10;
while(i>0)
{
for(j=i-1;j>=0;j--)
{
if(a[j][i]!=x)
{
b=dist[i]-a[j][i];
if(b==dist[j])
{
path[k++]=j+1;
i=j;
break;
}
}
}
}
return k;
}*/
}</span>