算法:马只走K步跳到指定位置的方法数

题目描述

假设把整个棋盘放入第一象限,棋盘的最左下角是(0,0)位置,整个棋盘就是横坐标上10条线,纵坐标上9条线的区域,给出三个参数x,y,k;

返回“马”从(0,0)位置出发,必须走K步,最终落在(x,y)上的方法数有多少种?

比如:K = 10;x = 7;y = 7 ,返回多少种方法?

题目解析

暴力递归

马在一个位置可以有八种选择

在这里插入图片描述

当小马在一个位置时:

  • 第一步:判断当前位置是否合法,如果不合法,返回0(表示不可能到达目的地)
  • 第二步:如果当前没有步数了,此时判断是否已经到达了指定位置,如果是,那么返回1
  • 第四步:否则,有8个方向可以选择,将这些方法全部加起来就是最终答案

代码如下:

class Solution {
    // 当前来到的位置是(x,y)还剩下rest步需要跳
    // 跳完rest步,正好跳到a,b的方法数是多少?
 	// 横坐标上10条线,纵坐标上9条线的区域
    int process(int x, int y, int rest, int a, int b) {
        if (x < 0 || x > 9 || y < 0 || y > 8) {
            return 0;
        }
        if (rest == 0) {
            return (x == a && y == b) ? 1 : 0;
        }
        int ways = process(x + 2, y + 1, rest - 1, a, b);
        ways += process(x + 1, y + 2, rest - 1, a, b);
        ways += process(x - 1, y + 2, rest - 1, a, b);
        ways += process(x - 2, y + 1, rest - 1, a, b);
        ways += process(x - 2, y - 1, rest - 1, a, b);
        ways += process(x - 1, y - 2, rest - 1, a, b);
        ways += process(x + 1, y - 2, rest - 1, a, b);
        ways += process(x + 2, y - 1, rest - 1, a, b);
        return ways;
    }
    
public :
    int jump(int a, int b, int k) {
        return process(0, 0, k, a, b);
    }
};

测试:

   std::cout << a.jump(2, 3, 3);  //4

暴力递归改备忘录

我们来分析下递归函数的可变参数与变化范围

  int process(int x, int y, int rest, int a, int b) 

这里的可变参数有x、y、rest,一共有三个,说明要准备一个三维数组

在这里插入图片描述
横坐标上9条线,纵坐标上10条线的区域,因此 c a c h e [ 9 ] [ 10 ] [ K + 1 ] cache[9][10][K + 1] cache[9][10][K+1]

class Solution {
    // 当前来到的位置是(x,y)还剩下rest步需要跳
    // 跳完rest步,正好跳到a,b的方法数是多少?
    int process(int x, int y, int rest, int a, int b,  std::vector<std::vector<std::vector<int>>> cache) {
        if (x < 0 || x > 9 || y < 0 || y > 8){
            return 0;
        }
        if(cache[x][y][rest] != -1){
            return cache[x][y][rest];
        }
        if (rest == 0) {
            return (x == a && y == b) ? 1 : 0;
        }
        int ways = process(x + 2, y + 1, rest - 1, a, b, cache);
        ways += process(x + 1, y + 2, rest - 1, a, b, cache);
        ways += process(x - 1, y + 2, rest - 1, a, b, cache);
        ways += process(x - 2, y + 1, rest - 1, a, b, cache);
        ways += process(x - 2, y - 1, rest - 1, a, b, cache);
        ways += process(x - 1, y - 2, rest - 1, a, b, cache);
        ways += process(x + 1, y - 2, rest - 1, a, b, cache);
        ways += process(x + 2, y - 1, rest - 1, a, b, cache);
        cache[x][y][rest] = ways;
        return cache[x][y][rest];
    }

public :
    int jump(int a, int b, int k) {

        std::vector<std::vector<std::vector<int>>> cache(10,
                std::vector<std::vector<int>>(9,
                        std::vector<int>(k + 1, -1)));
        return process(0, 0, k, a, b, cache);
    }
};

int main(){
   // printf("%d\r\n",  Fab(4));


   Solution a;
   vector<int> nums {1, 2, 3, 1};

   std::cout << a.jump(2, 3, 7);

}

暴力递归改动态规划

(1)准备一张表,递归函数的可变参数与变化范围

  int process(int x, int y, int rest, int a, int b) 

这里的可变参数有x、y、rest,一共有三个,说明要准备一个三维数组

  • x取值范围为:0~10
  • y取值范围为:0~9
  • rest取值范围为:0~k

所以,准备数组如下:

std::vector<std::vector<std::vector<int>>> dp(10,
                std::vector<std::vector<int>>(9,
                        std::vector<int>(k + 1, 0)));

(2)返回值。分析主函数是如何调用递归函数的

        return process(0, 0, k, a, b, cache);

所以应该返回:dp[0][0][k]

(3)填表
(3.1)第一步,应该确保参数合法

 if (x < 0 || x > 9 || y < 0 || y > 8){
            return 0;
        }

(3.2)第二步,使用base case来初始化这张表

	   if (rest == 0) {
            return (x == a && y == b) ? 1 : 0;
        }
  • 此时k为0,也就是初始化最底层的那个平面的某个点,也就是dp[a][b][0] = 1

在这里插入图片描述
(3.3)分析普通情况。由

   int ways = process(x + 2, y + 1, rest - 1, a, b, cache);
        ways += process(x + 1, y + 2, rest - 1, a, b, cache);
        ways += process(x - 1, y + 2, rest - 1, a, b, cache);
        ways += process(x - 2, y + 1, rest - 1, a, b, cache);
        ways += process(x - 2, y - 1, rest - 1, a, b, cache);
        ways += process(x - 1, y - 2, rest - 1, a, b, cache);
        ways += process(x + 1, y - 2, rest - 1, a, b, cache);
        ways += process(x + 2, y - 1, rest - 1, a, b, cache);

上一层的dp[x][y][k]依赖下一层平面的八个方向dp[?][?][k-1],当然我们必须确保[?][?]合法

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

class Solution {

    int pick(std::vector<std::vector<std::vector<int>>> &dp, int x, int y, int rest) {
        if (x < 0 || x > 9 || y < 0 || y > 8){
            return 0;
        }
        return dp[x][y][rest];
    }


public :
    int jump(int a, int b, int k) {
        std::vector<std::vector<std::vector<int>>> dp(10,
                                                         std::vector<std::vector<int>>(9,
                                                                                       std::vector<int>(k + 1, 0)));
        dp[a][b][0] = 1;
        for (int rest = 1; rest <= k; ++rest) {
            for (int x = 0; x < 10; ++x) {
                for (int y = 0; y < 9; ++y) {
                    int ways = pick(dp, x + 2, y + 1, rest - 1);
                    ways += pick(dp, x + 1, y + 2, rest - 1);
                    ways += pick(dp, x - 1, y + 2, rest - 1);
                    ways += pick(dp, x - 2, y + 1, rest - 1);
                    ways += pick(dp, x - 2, y - 1, rest - 1);
                    ways += pick(dp, x - 1, y - 2, rest - 1);
                    ways += pick(dp, x + 1, y - 2, rest - 1);
                    ways += pick(dp, x + 2, y - 1, rest - 1);
                    dp[x][y][rest] = ways;
                }
            }
        }
        return dp[0][0][k];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值