跳马问题:马走日,请问马从0,0位置出发,走到x,y目标点,还必须走k步,有多少种走法

跳马问题:马走日,请问马从0,0位置出发,走到x,y目标点,还必须走k步,有多少种走法?

提示:这个题目,被字节跳动改编了,然后放笔试里面考!!
所以好好联系这些题目,都是大厂爱考的算法原型
提示:互联网大厂的经典动态规划题

解决动态规划问题的重要知识!暴力递归的4种经典尝试模型
最关键的就是这个递归函数,解题的核心在这。
互联网大厂的动态规划题目中的四种经典暴力递归尝试模型:
(1)DP1:从左往右的尝试模型,关注i位置结尾,或者i位置开头的情况,或者看i联合i+1,i+2的情况,填表往往是上到下,或者下到上,左到右,右到左。
(2)DP2:从L–R范围上的尝试模型,关注L和R的情况,填表格式非常固定,主对角,副对角,倒回来填
(3)DP3:多样本位置对应的尝试模型,2个样本,一个样本做行,一个样本做列,关注i和j对应位置的情况,先填边界,再填中间
(4)DP4:业务限制类的尝试模型,比如走棋盘,固定的几个方向可以走,先填边界,再填中间。

本题是——DP4:业务限制类的尝试模型


题目

跳马问题:10*10的棋盘放好,马只能在棋盘中走日的几个方向,
请问马从0,0位置出发,走到棋盘中的x,y目标点,还必须走k步,有多少种走法?


一、审题

示例:
10*10的棋盘放好,马只能走8个方向,日形状走,比如下面在xy处(绿色点)的马,下一次只能选择粉色点这8个方向试试:
他们分别是:
x - 2,y + 1
x - 1,y + 2
x + 1,y + 2
x + 2,y +1
x + 2,y - 1
x + 1,y - 2
x - 1,y - 2
x - 2,y - 1
在这里插入图片描述
现在从00出发,去xy,必须走k步,有多少种方案?


暴力递归函数的尝试:DP4业务限制模型

看示例那个棋盘,显然有三个变量,咱们这么想
从00去xy,等价于从xy去00,走法一样对吧?

所有,从xy出发,必须走k步,去00点,有多少种走法?可以定义为暴力递归函数:
f(arr,x,y,k)
arr是棋盘,xy是当前位置,还剩余k步,去00点,有多少种走法?
(0)一旦进f,请问你此刻x,y还合法吗???不合法,你这个方案非法,返回0种,没用
(1)当k=0了,迈不动了,如果此刻真的在x=0,y=0处,OK,成功来了,算合法方案,返回1,否则就是非法的方案,返回0
(2)当然在任意xy处,还有k步必须走,那就走:马走日,8个方向全部试探一下,能成功的话,他们返回来的方案数累加和就是咱要的结果,返回ans。

这个解题思路还是很简单的吧!!!
手撕代码:

//复习:
    //所有,从xy出发,必须走k步,去00点,有多少种走法?可以定义为暴力递归函数:
    //f(arr,x,y,k)
    //arr是棋盘,xy是当前位置,还剩余k步,去00点,有多少种走法?
    public static int f(int x, int y, int k){
        //(0)一旦进f,请问你此刻x,y还合法吗???不合法,你这个方案非法,返回0种,没用
        if (x < 0 || x > 9 || y < 0 || y >= 9) return 0;
        //(1)当k=0了,迈不动了,如果此刻真的在x=0,y=0处,
        // OK,成功来了,算合法方案,返回1,否则就是非法的方案,返回0
        if (k == 0) return x == 0 && y == 0 ? 1 : 0;

        int ans = 0;
        //(2)当然在任意xy处,还有k步必须走,那就走:马走日,8个方向全部试探一下,
        //x - 2,y + 1
        //x - 1,y + 2
        //x + 1,y + 2
        //x + 2,y + 1
        //x + 2,y - 1
        //x + 1,y - 2
        //x - 1,y - 2
        //x - 2,y - 1
        // 能成功的话,他们返回来的方案数累加和就是咱要的结果,返回ans。
        ans += f(x - 2,y + 1, k - 1) +
                f(x - 1,y + 2, k - 1) +
                f(x + 1,y + 2, k - 1) +
                f(x + 2,y + 1, k - 1) +
                f(x + 2,y - 1, k - 1) +
                f(x + 1,y - 2, k - 1) +
                f(x - 1,y - 2, k - 1) +
                f(x - 2,y - 1, k - 1);

        return ans;
    }


    public static void test(){
        System.out.println(go(2, 3, 3));
        System.out.println(f(2, 3, 3));
    }

这很容易就想到了
尝试,这是8个方向的业务,限制好的
所谓DP4:业务限制类型的尝试模型!

笔试AC解:根据暴力递归,改傻缓存dp表跟随暴力递归,记忆化搜索方法

看得出来,暴力递归函数中,xy和k仨是3个变量,这是咱们目前遇到的最复杂的变量,3个呢

但是也很简单
xy是棋盘平面,k是z轴,好说
就是要填下面这么个三维表dp而已
xy取0–9
k取0–k
在这里插入图片描述
咱们准备这个表dp,跟对f,求过了,返回
没求过,重新求,放入dp存起来,动态规划已然OK

手撕代码!

    //复习:记忆化搜索法:傻缓存dp跟随f
    //所有,从xy出发,必须走k步,去00点,有多少种走法?可以定义为暴力递归函数:
    //f(arr,x,y,k)
    //arr是棋盘,xy是当前位置,还剩余k步,去00点,有多少种走法?
    public static int fDP(int x, int y, int k, int[][][] dp){
        //(0)一旦进f,请问你此刻x,y还合法吗???不合法,你这个方案非法,返回0种,没用
        if (x < 0 || x > 9 || y < 0 || y >= 9) return 0;

        if (dp[x][y][k] != -1) return dp[x][y][k];//求过了

        //(1)当k=0了,迈不动了,如果此刻真的在x=0,y=0处,
        // OK,成功来了,算合法方案,返回1,否则就是非法的方案,返回0
        if (k == 0) {
            dp[x][y][k] = x == 0 && y == 0 ? 1 : 0;
            return dp[x][y][k];
        }

        int ans = 0;
        //(2)当然在任意xy处,还有k步必须走,那就走:马走日,8个方向全部试探一下,
        //x - 2,y + 1
        //x - 1,y + 2
        //x + 1,y + 2
        //x + 2,y + 1
        //x + 2,y - 1
        //x + 1,y - 2
        //x - 1,y - 2
        //x - 2,y - 1
        // 能成功的话,他们返回来的方案数累加和就是咱要的结果,返回ans。
        ans += fDP(x - 2,y + 1, k - 1, dp) +
                fDP(x - 1,y + 2, k - 1, dp) +
                fDP(x + 1,y + 2, k - 1, dp) +
                fDP(x + 2,y + 1, k - 1, dp) +
                fDP(x + 2,y - 1, k - 1, dp) +
                fDP(x + 1,y - 2, k - 1, dp) +
                fDP(x - 1,y - 2, k - 1, dp) +
                fDP(x - 2,y - 1, k - 1, dp);

        dp[x][y][k] = ans;

        return dp[x][y][k];
    }
    public static int fDPwithdp(int x, int y, int k){
        if (x < 0 || x > 9 || y < 0 || y >= 9) return 0;

        int[][][] dp = new int[10][10][k + 1];
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 10; j++) {
                for (int l = 0; l < k + 1; l++) {
                    dp[i][j][l] = -1;//初始化
                }
            }
        }

        return fDP(x, y, k, dp);
    }

    public static void test(){
        System.out.println(go(2, 3, 3));
        System.out.println(f(2, 3, 3));
        System.out.println(fDPwithdp(2, 3, 3));
    }

测试问题不大:

4
4
4

这样的话,笔试快速AC!

面试精华解:根据暴力递归,改动态规划表!

老样子,咱们要根据暴力递归改写动态规划的转移方程,不要用暴力递归求了,直接填表dp返回dp

dp是这样的:在xy处剩余k步,去00有多少种走法?

xy取0–9
k取0–k
在这里插入图片描述
咱们最后要dp[x][y][k],图中红色五角星那个格子

手撕代码,完全根据暴力递归改写转移方程,所以上面暴力递归尝试最重要!!!

根据:f(arr,x,y,k)arr是棋盘10*10,xy是当前位置,还剩余k步,去00点,有多少种走法?
(0)一旦进f,请问你此刻x,y还合法吗???不合法,你这个方案非法,返回0种,没用
(1)当k=0了,迈不动了,如果此刻真的在x=0,y=0处,OK,成功来了,算合法方案,返回1,否则就是非法的方案,返回0

if (k == 0) return x == 0 && y == 0 ? 1 : 0;

咱们可以把0 0 0原点格子填好为1

(2)当然在任意xy处,还有k步必须走,那就走:马走日,8个方向全部试探一下,能成功的话,他们返回来的方案数累加和就是咱要的结果,返回ans。

ans += f(x - 2,y + 1, k - 1) +
                f(x - 1,y + 2, k - 1) +
                f(x + 1,y + 2, k - 1) +
                f(x + 2,y + 1, k - 1) +
                f(x + 2,y - 1, k - 1) +
                f(x + 1,y - 2, k - 1) +
                f(x - 1,y - 2, k - 1) +
                f(x - 2,y - 1, k - 1);

其余任意位置dp[x][y][k]依赖8个方向,当然,那些方向必须要是合法的格子
所以咱们先整一个填格子的函数
如果xy不越界,咱们获取那个方向的值回来

public static int getValue(int x, int y, int k, int[][][] dp){
        //控制越界
        if (x < 0 || x > 9 || y < 0 || y > 8) return 0;
        return dp[x][y][k];//不越界就返回下一次的值
    }

OK,咱慢来手撕代码填写dp!!!宏观调度是啥呢?xy是一个面,咱们要调度,不同的k,也就是不同的z轴,相同的xy有啥情况

所以,先从k调度
再填写xy

//其余任意点
        for (int l = 1; l < k + 1; l++) {//注意,k=0有了,求k一面一面增
            for (int i = 0; i < 10; i++) {
                for (int j = 1; j < 10; l++) {

整体代码:

//面试精华解:根据暴力递归,改动态规划表!
    public static int horseGoDp(int x, int y, int k){
        if (x < 0 || x > 9 || y < 0 || y > 9) return 0;

        int[][][] dp = new int[10][10][k + 1];

        //完全根据暴力递归改写
        //(1)当k=0了,迈不动了,如果此刻真的在x=0,y=0处,
        // OK,成功来了,算合法方案,返回1,否则就是非法的方案,返回0
        dp[0][0][0] = 1;//原点就是1,因为xyk都为0--这个面,其他的就是0

        //其余任意点
        for (int l = 1; l < k + 1; l++) {//注意,k=0有了,求k一面一面增
            for (int i = 0; i < 10; i++) {
                for (int j = 0; j < 10; j++) {
                    //暴力递归怎么搞,咱就怎么改写
                    int ans = 0;
                    //(2)当然在任意xy处,还有k步必须走,那就走:马走日,8个方向全部试探一下,
                    //x - 2,y + 1
                    //x - 1,y + 2
                    //x + 1,y + 2
                    //x + 2,y + 1
                    //x + 2,y - 1
                    //x + 1,y - 2
                    //x - 1,y - 2
                    //x - 2,y - 1
                    // 能成功的话,他们返回来的方案数累加和就是咱要的结果,返回ans。
                    ans += getValueValid(i - 2,j + 1, l - 1, dp) +
                            getValueValid(i - 1,j + 2, l - 1, dp) +
                            getValueValid(i + 1,j + 2, l - 1, dp) +
                            getValueValid(i + 2,j + 1, l - 1, dp) +
                            getValueValid(i + 2,j - 1, l - 1, dp) +
                            getValueValid(i + 1,j - 2, l - 1, dp) +
                            getValueValid(i - 1,j - 2, l - 1, dp) +
                            getValueValid(i - 2,j - 1, l - 1, dp);

                    dp[i][j][l] = ans;
                }
            }
        }

        return dp[x][y][k];
    }
    public static int getValueValid(int x, int y, int k, int[][][] dp){
        //合法才能拿到dp那些位置的值
        if (x < 0 || x > 9 || y < 0 || y > 9) return 0;//不合法

        return dp[x][y][k];
    }

    public static void test2(){
        System.out.println(go2(2, 3, 3));
        System.out.println(horseGoDp(2, 3, 3));
    }

    public static void main(String[] args) {
//        test();
        test2();
    }

结果OK:

4
4

总结

提示:重要经验:

1)任何动态规划,都有一个DP模型,最关键的就是尝试模型了,把暴力递归搞出来
2)后续改记忆化搜索方法也好,改精细化动态规划填表也罢,都非常容易
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值