剑指offer-机器人的运动范围

剑指offer上的题目,在牛客网上刷题通过,下面记录相关思路。

题目描述

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

问题分析

这是一个典型的回溯法问题,首先我们需要明确问题的解空间并确定解空间的结构,以便回溯搜索求解。解空间是一个m x n的向量组,表示对应方格中的每一个格子是否能够达到,分别用0和1标记这两种状态,解空间的结构采用二维数组极为方便,递归前将其初始化为全0状态。

回溯的过程采用递归实现时较为容易,思路如下:

  • 判断当前坐标是否可到达,需要满足一下两个条件:
    • 当前所在坐标数位和不超过阈值
    • 解向量中该坐标处元素值为0
  • 若不可到达返回0,若可到达将向量组中该处元素置为1
  • 继续对该点上(i,j+1)下(i,j-1)左(i-1,j)右(i+1,j)四个点递归上述步骤
  • 递归出口是坐标值不超过矩阵四个边界

    程序代码

    代码采用JavaScript编写,运行在Node 6.11.4环境中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    /**
    * 构造二维数组
    *
    * @param {Number} rows 数组行数
    * @param {Number} cols 数组列数
    * @param {Number} initial 元素初始值
    * @returns {Array} 构造的新数组
    */
    function matrix(rows, cols, initial) {
    const arr = [];
    for (let i = 0; i < rows; i++) {
    const columns = [];
    for (let j = 0; j < cols; j++) {
    columns[j] = initial;
    }
    arr[i] = columns;
    }
    return arr;
    }

    /**
    * 计算整数各个数位的数字之和
    *
    * @param {Number} number 待计算的整数
    * @returns {Number} 数位和
    */
    function getSum(number) {
    let res = 0;
    let num = number || 0;
    while (num > 0) {
    res += num % 10;
    num = parseInt(num / 10, 10);
    }
    return res;
    }

    /**
    * 回溯法主体
    *
    * @param {NUmber} i 横坐标
    * @param {NUmber} j 纵坐标
    * @param {Number} rows 矩阵行数
    * @param {Number} cols 矩阵列数
    * @param {Array} flag 解向量组
    * @param {Number} threshold 题目限制条件阀值
    * @returns {Number} 可到达点的个数
    */
    function backTrack(i, j, rows, cols, flag, threshold) {
    const flags = flag || [];
    // 递归出口为整个矩阵的四个边界
    // 可到达满足两个条件:1.数位和小于阀值,2.该位置未被标记过
    if (i < 0 || i >= rows || j < 0 || j >= cols ||
    getSum(i) + getSum(j) > threshold || flags[i][j] === 1
    ) {
    return 0;
    }

    // 标记当前位置为可到达
    flags[i][j] = 1;
    // 继续递归上下左右的方格
    return backTrack(i - 1, j, rows, cols, flags, threshold) +
    backTrack(i + 1, j, rows, cols, flags, threshold) +
    backTrack(i, j - 1, rows, cols, flags, threshold) +
    backTrack(i, j + 1, rows, cols, flags, threshold) +
    1;
    }

    /**
    * 机器人的运动范围,采用回溯法
    *
    * @param {Number} threshold 题目限制条件阀值
    * @param {Number} rows 矩阵行数
    * @param {Number} cols 矩阵列数
    * @returns {Number} 可到达点的个数
    */
    function movingCount(threshold, rows, cols) {
    const flag = matrix(rows, cols, 0);
    return backTrack(0, 0, rows, cols, flag, threshold);
    }

    module.exports = movingCount;

补充说明

该问题旨在求能够到达多少个格子,而机器人在上下左右四个方向上都可以运动,并且整块方格又是连通的,所以不需要记录路径只需要统计个数,即不满足要求的点跳过即可不需要回溯,故在代码思路中没有回溯的步骤,但这并不妨碍对回溯法整体的理解。另外该问题还可以采用其他方法求解,如bfs、dp等。