1. 机器人的运动范围

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

  1. 示例 1
  2. 输入:m = 2, n = 3, k = 1
  3. 输出:3
  4. 示例 2
  5. 输入:m = 3, n = 1, k = 0
  6. 输出:1
  7. 提示:
  8. 1 <= n,m <= 100
  9. 0 <= k <= 20

思路: 广度优先搜索

我们将行坐标和列坐标数位之和大于 k 的格子看作障碍物,那么这道题就是一道很传统的搜索题目,我们可以使用广度优先搜索或者深度优先搜索来解决它,本文选择使用广度优先搜索的方法来讲解。

那么如何计算一个数的数位之和呢?我们只需要对数 x 每次对 10 取余,就能知道数 x 的个位数是多少,然后再将 x 除 10,这个操作等价于将 x 的十进制数向右移一位,删除个位数(类似于二进制中的 >> 右移运算符),不断重复直到 x 为 0 时结束。

同时这道题还有一个隐藏的优化:我们在搜索的过程中搜索方向可以缩减为向右和向下,而不必再向上和向左进行搜索。如下图,我们展示了 16 * 16 的地图随着限制条件 k 的放大,可行方格的变化趋势,每个格子里的值为行坐标和列坐标的数位之和,蓝色方格代表非障碍方格,即其值小于等于当前的限制条件 k。我们可以发现随着限制条件 k 的增大,(0, 0) 所在的蓝色方格区域内新加入的非障碍方格都可以由上方或左方的格子移动一步得到。而其他不连通的蓝色方格区域会随着 k 的增大而连通,且连通的时候也是由上方或左方的格子移动一步得到,因此我们可以将我们的搜索方向缩减为向右或向下。

class Solution {
    public int movingCount(int m, int n, int k) {
        if (k == 0) {
            return 1;
        }
        // 用来存放当前找到的点,以便于下一次查找
        Queue<int[]> queue = new LinkedList<int[]>();
        // 向右和向下的方向数组
        int[] dx = {0, 1};
        int[] dy = {1, 0};
        // *******用来标记已经走过的地方,并且是满足条件的坐标
        boolean[][] vis = new boolean[m][n];
        // (0,0) 满足任何m,n的要求
        queue.offer(new int[]{0, 0});
        vis[0][0] = true;
        int ans = 1;
        // 当队列为空,无法查找
        while (!queue.isEmpty()) {
            int[] cell = queue.poll();
            int x = cell[0], y = cell[1];
            for (int i = 0; i < 2; ++i) {
                int tx = dx[i] + x;
                int ty = dy[i] + y;
                // 当已经走过,出了边界,或者不满足<=k, 直接进入下一次循环
                if (tx < 0 || tx >= m || ty < 0 || ty >= n || vis[tx][ty] || get(tx) + get(ty) > k) {
                    continue;
                }
                queue.offer(new int[]{tx, ty});
                vis[tx][ty] = true;
                ans++;
            }
        }
        return ans;
    }

    // 用于获取一个数字的所有位数之和
    private int get(int x) {
        int res = 0;
        while (x != 0) {
            res += x % 10;
            x /= 10;
        }
        return res;
    }
}

广度优先搜索算法(最短路径问题)

一. 简介

  • 广度优先搜索算法(Breadth-First Search,BFS)是一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,就是它不考虑结果的可能位置,是彻底地搜索整张地图,知道找到结果为止。BFS并不使用经验法则算法。

  • 广度优先搜索可以找到关系网中是否有哪个人,并且找到最短的关系路径(或者说可以找到多个结点图中两个节点是否有关系,并且找到最短的路径)。

  • 应用场景:

    • 国际跳棋中,计算最短的获胜步数。
    • 根据你的人际关系网络找到关系最近的医生。
    • 出发地到目的地搭乘公交车经过换乘点最少的路线。

      二. 具体实例

  • 搭乘公交车

    • 使用广度优先搜索,会先找你的当前站点挨着的所有站点;
    • 然后第二次搜索会找寻你上一步所有站点的紧挨着所有的站点,并且不往回找;
    • 一层一层直到找到目的地,并且当前从出发地到搜索多次第一次出现目的地时,这个路线为最短
  • 从双子峰出发,目的地是金门大桥

image.png

  • 第一步首先找到一步能到的地方,此时未找到金门大桥

image.png

  • 第二次搜索,找走一步能到的站点

image.png

  • 此时仍未找到,那么继续搜索

image.png

  • 第三步找到目的地,完成。此时可知最短换乘站数是3,即:到达目的地最短需要三步。

三. 注意

  • 这种问题被称为最短路径问题(shorterst-path problem)。你经常要找出最短路径,前往朋友家的最短路径,后者国际象棋中多少步可以把对方将死的最小步数。解决这类问题就需要使用广度优先搜索。
    • 使用图来建立问题模型
    • 使用广度优先搜索解决问题
  • 第一层搜索的关系或者说是节点,会是第二层的执行搜索节点,这时需要一个容器进行存储;并且保证顺序存储时,能按顺序取出,那么这就需要一种数据结果,那就是队列。

四. 队列

  • 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
  • 队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。

    顺序队列
  • 建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置


  • 每次在队尾插入一个元素是,rear增1;每次在队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元

  • 顺序队列中的溢出现象:

    • “下溢”现象:当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。
    • “真上溢”现象:当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。
    • “假上溢”现象:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为”假上溢”现象。

循环队列
  • 在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1或front指针增1 时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。自己真从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。除了一些简单应用之外,真正实用的队列是循环队列。

  • 在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件时front=rear,而队列判满的条件时front=(rear+1)%MaxSize。队空和队满的情况如图:

image.png