【算法基础】暴力递归转动态规划

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));
    }



}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值