每天一道动态规划——第一天
动态规划一定要去尝试!
题目一:
1)题目描述
一共有N个位置,机器人从当前位置cur走到目标位置aim,有res步可以走,问一共有多少种方法。
题目示例:
1 2 3 4 5 6
N=6|cur=2|aim=3|res=3
结果:1
分析:2-3-4-3
2)代码
public class demo01 {
public static void main(String[] args) {
int res = 13;
int cur = 2;
int aim = 3;
int N = 7;
int result = 0;
result = process1(cur, aim, res, N);
System.out.println(result);
int[][] dp = new int[N + 1][res + 1];
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= res; j++) {
dp[i][j] = -1;
}
}
int result2=process2(cur, aim, res, N,dp);
System.out.println(result2);
}
public static int process1(int cur, int aim, int res, int N) {
/*
cur:当前位置
aim:目标位置
res:剩余步数
N:共有哪些位置
*/
if (res == 0) {
return cur == aim ? 1 : 0;
}
if (cur == 1) {
return process1(2, aim, res - 1, N);
} else if (cur == N) {
return process1(N - 1, aim, res - 1, N);
} else {
return process1(cur - 1, aim, res - 1, N) + process1(cur + 1, aim, res - 1, N);
}
}
}
3)代码优化
当暴力递归使总是出现重复解,可以通过空间换时间的方法来进行优化。
但当一个暴力递归中不存在重复解,那么它本身就是不可以优化的。
我们总说动态规划的dp表,我觉得最好的理解就是缓存表。
我们可以发现,结果是由cur与res唯一决定的(此时我认为是由状态唯一决定的)。
如果你之前计算过cur还剩res步的时候,此时又要计算,那为什么不用一个二元组把数据记录下来呢?记为dp[cur][res]。
cur的范围为:1~N
res的范围为:0~K
所以对二元状态表进行初始化:
-1为没计算过;
!=-1为计算过;—有缓存先走缓存
优化代码如下:
package com.sq.demo72DynamicPlan;
public class demo01 {
static int res = 3;
static int cur = 2;
static int aim = 3;
static int N = 7;
public static void main(String[] args) {
long start = System.nanoTime();
int[][] dp = new int[N + 1][res + 1];
for (int i = 0; i <= N; i++) {
for (int j = 0; j <= res; j++) {
dp[i][j] = -1;
}
}
int result2 = process2(cur, aim, res, N, dp);
long end = System.nanoTime();
System.out.println(result2);
System.out.println(end - start);
}
public static int process2(int cur, int aim, int res, int N, int[][] dp) {
/*
cur:当前位置
aim:目标位置
res:剩余步数
N:共有哪些位置
*/
if (dp[cur][res] != -1) {
return dp[cur][res];
}
int ans = 0;
if (res == 0) {
ans = (cur == aim ? 1 : 0);
} else if (cur == 1) {
ans = process2(2, aim, res - 1, N, dp);
} else if (cur == N) {
ans = process2(N - 1, aim, res - 1, N, dp);
} else {
ans = process2(cur - 1, aim, res - 1, N, dp) + process2(cur + 1, aim, res - 1, N, dp);
}
dp[cur][res] = ans;
return ans;
}
}
这种动态规划就是傻缓存,我并不关心你之前的状态,你只要给我当前的状态我就会给你个输出。就像是你只需要输入银行卡号和密码,银行就给你钱,他不关心你跟这个银行卡号和密码的故事。
这种方法的学名叫做:从顶向下的动态规划。 或者缓存法搜索。
4)代码优化2
上面这张图虽然画的很乱,但是很明了。
根据分析何以知道,如果
1 2 3 4 5
N=5|cur=2|aim=4|res=6
在这种情况下,在res=0,cur=aim=4的时候方法才为1.
所以第4行第0列=1,即dp[4][0]=1;第0列的其余值均为0,即dp[i][0]=0,i≠4。
此处就相当于一个初始条件,也是我们之前学递归时候说的递归结束条件。
那我们就不需要递归调用了,只需要根据表格的初始情况以及一些规律,把整张表格填写好了,就可以得出我们想要的结果了。
话不多说,上代码:
public class demo03 {
static int res = 23;
static int cur = 2;
static int aim = 3;
static int N = 7;
public static void main(String[] args) {
ways3(cur, aim, res, N);
}
public static void ways3(int cur, int aim, int res, int N) {
System.out.println("第三种方式");
long start = System.nanoTime();
int[][] dp = new int[N + 1][res + 1];
//第0列是初始条件
dp[aim][0] = 1;//第0列其余的位置都是0
for (int i = 1; i <= res; i++) {
dp[1][i] = dp[2][i - 1];
for (int j = 2; j < N; j++) {
dp[j][i] = dp[j - 1][i - 1] + dp[j + 1][i - 1];
}
dp[N][i] = dp[N - 1][i - 1];
}
long end = System.nanoTime();
System.out.println("共有方式:"+dp[cur][res]+"种");
System.out.println("耗时为(ns):"+(end-start));
}
}
初步入门动态规划就是这些了,我们接下来都要从初始的暴力动态规划,一步步推到这种状态转移过程,加油!!!