package class18;
/**
* Desc: 暴力递归转动态规划、
* 动态规划的核心主要为:
* 1、先暴力递归
* 2、然后若有重复计算时,使用缓存记录
* 3、根据缓存表,生成动态规划,生成动态规划的主要思路就是,如何逆推倒推缓存表,后获取结果可直接从缓存表中获取!
*
*
* 假设有排成一行的N个位置,记为1~N,N一定大于或等于2
* 开始时机器人在其中的M位置上(M一定是1~N中的一个)
* 如果机器人来到1位置,那么下一步只能往右来到2位置;
* 如果机器人来到N位置,那么下一步只能往左来到N-1位置;
* 如果机器人来到中间位置,那么下一步可以往左走或者往右走;
* 规定机器人必须走K步,最终能来到P位置(P也是1~中的一个)的方法有多少种
* start:开始位置 aim:目标位置 k:步数 N:总位置数
*
*/
public class ViolenceRecursive {
public static int wrap1(int start, int aim, int k, int N) {
return f1(start, k, aim, N);
}
// 首先先写一个暴力递归的方法
// cur: 当前位置 reset:剩余步数 aim:目标位置 n:可走位置
public static int f1(int cur, int reset, int aim, int N) {
// 首先先写base case,基础位置,跳出递归的位置
// 剩余步数如果为0,则此时不需要再走了,直接返回即可
if (reset == 0) {
return cur == aim ? 1 : 0;
}
// 如果当前位置是在开头,则此时只能往右走
if (cur == 1) {
return f1(2, reset - 1, aim, N);
}
// 如果当前位置是在最后,则此时只能往左边走
if (cur == N) {
return f1(N-1, reset - 1, aim, N);
}
// 如果当前位置是在中间的话,此时可以王左也可以往右,则需要记录两边的方法
return f1(cur - 1, reset - 1, aim, N) + f1(cur + 1, reset - 1, aim, N);
}
// 然而直接暴力递归会存在如下问题
// 如,进行(cur, reset)时,会存在重复计算,如 (3,4),会执行 (2,3) (4,3), 那(1,4)也会执行(2,3),就导致重复了
// 所以此时应该找到关系因子,当前问题的关系因子就是 当前位置和剩余步数 然后使用表缓存关键因子即可
// 此时使用二维表存储
public static int wrap2(int start, int aim, int k, int N) {
// 由于关系因子时 当前位置和剩余步数 -> 值为 方法次数,所以可以使用二维表进行存储
int[][] dp = new int[N+1][k+1];
// 将值都设置为-1,主要用于判断当前位置是否已经记录过
for (int i = 0; i < dp.length; i++) {
for (int j = 0; j < dp[0].length; j++) {
dp[i][j] = -1;
}
}
return f2(start, k, aim, N, dp);
}
public static int f2(int cur, int reset, int aim, int N, int[][] dp) {
// 首先如果当前位置已经走过的了话,不需要再往下递归
if (dp[cur][reset] != -1) {
return dp[cur][reset];
}
int ans = 0;
if (reset == 0) {
ans = cur == aim ? 1 : 0;
} else if (cur == 1) {
ans = f2(2, reset - 1, aim, N, dp);
} else if (cur == N) {
ans = f2(N - 1, reset - 1, aim, N, dp);
} else {
ans = f2(cur - 1, reset - 1, aim, N, dp) + f2(cur + 1, reset - 1, aim, N, dp);
}
dp[cur][reset] = ans;
return ans;
}
// 然后,使用了缓存表之后,此时还会存在一个问题,就是使用了空间换取了时间,当然在普通情况是没问题的,但是我们可以使用动态规划
// 动态规划转换的话,就是 逆推,倒推 从缓存表中找到规律,然后直接循环实现动态规划
public static int wrap3(int start, int aim, int k, int N) {
// 通过解析dp缓存表,并且根据暴力递归的规律,可以直接生成dp表,然后返回即可
// 首先生成dp表
int[][] dp = new int[N+1][k+1];
// 首先根据暴力递归的base case,生成最基础列
// 就是当剩余步数为0时, aim == cur时才会有方法走完,并且方法数为1
dp[aim][0] = 1;
// 然后循环遍历表,规律生成dp表(目前可按列来生成) 此处为动态规划的核心
for (int j = 1; j < dp[0].length; j++) {
// 然后再遍历行逐步加上去
// 首先 第一行只需要拿到第二行的上一列的值, 对应 f2(2, reset - 1, aim, N, dp);
dp[1][j] = dp[2][j-1];
for (int i = 2; i < N; i++) {
// 对应 f1(cur - 1, reset - 1, aim, N) + f1(cur + 1, reset - 1, aim, N);
dp[i][j] = dp[i-1][j-1] + dp[i+1][j-1];
}
// 最后一行的话,获取的是上一行的上一列值,对应 f2(N - 1, reset - 1, aim, N, dp);
dp[N][j] = dp[N-1][j-1];
}
// 直接返回缓存表中值即可
return dp[start][k];
}
public static void main(String[] args) {
System.out.println(wrap1(2, 4, 6, 5));
System.out.println(wrap2(2,4,6,5));
System.out.println(wrap3(2,4,6,5));
}
}
【算法基础】暴力递归转动态规划
最新推荐文章于 2024-11-03 21:27:26 发布