深度 / 广度优先算法题总结

1 剑指 Offer 13. 机器人的运动范围

地上有一个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:

输入:m = 2, n = 3, k = 1
输出:3

示例 2:

输入:m = 3, n = 1, k = 0
输出:1

提示:

1 <= n,m <= 100
0 <= k <= 20

1.1 解题思路

深度优先遍历(DFS)

深度优先搜索: 可以理解为暴力法模拟机器人在矩阵中的所有路径。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
剪枝: 在搜索中,遇到数位和超出目标值、此元素已访问,则应立即返回,称之为 可行性剪枝 。

深度优先需要递归,需要状态和子问题的概念。使用一个布尔类型的二维数组来记录状态(一个格子是否被访问过)。划分子问题,机器人能移动的格子数为,当前的格子的下边能到的所有格子数和右边能到的所有格子数的和,最后,再加1(加上当前格子),只要当前格子能够到达,那么这个格子的正上方格子和左边格子一定能够到达,所以不用考虑格子往上、左移。

另外,横座标和纵座标各位上的数字之和,可以用如下方法,假如s为横座标x各个位上数字的和,因为m 和 n 是大于1小于100的数,sx 与 x的上一个座标x - 1的各个位上数字的和s1的关系是,s = s % 10 == 0 ? s1 - 8 : s1 + 1,也就是,当横座标为10的倍数时,假如为20,数位和为2,而19的数位和为10,相差8,如果不是10的倍数,假如为15,数位和为6,14的数位和为5。

代码

class Solution {
    int m = 0;
    int n = 0;
    int k = 0;
    boolean[][] visited;
    public int movingCount(int m, int n, int k) {
        this.m = m;
        this.n = n;
        this.k = k;
        visited = new boolean[m][n];
        return this.dfs(0, 0, 0, 0);
    }

    public int dfs(int i, int j, int si, int sj){
        //递归结束的条件,越界,或者数位和大于k,或者,格子已经走过
        if(i == m || j == n || (si + sj) > k || visited[i][j] == true) return 0;
        visited[i][j] = true;
        
        //深读优先遍历,需要使用递归,划分子问题,机器人能够达到的格子数就是,当前格子的右边能到的的格子数,加上当前格子的左边能到达的
        //格子数,最后加1(加上当前格子),这道题不必考虑当前格子的左边格子和上面格子,因为当前格子符合的话,左边和上边格子一定符合。
        return 1 + dfs(i + 1, j, si = si % 10 == 0 ? si - 8 : si + 1, sj ) + dfs(
        	i, j + 1, si, sj = sj % 10 == 0 ? sj - 8 : sj + 1);
    }
}

广度优先遍历(BFS)

BFS/DFS : 两者目标都是遍历整个矩阵,不同点在于搜索顺序不同。DFS 是朝一个方向走到底,再回退,以此类推;BFS 则是按照“平推”的方式向前搜索。通常利用队列实现广度优先遍历。定义一个队列,队列中存储长度为4的int类型的数组,数组中存储的是格子的横座标、纵座标、横座标数位和、纵座标数位和,队列中存储的是符合条件的格子。先将第一个格子存到队列中,然后,判断队列是否为空,不为空的话,就一直循环,取出队首格子,判断这个格子是否符合条件,如果符合,就将这个格子的下方的格子和右边的格子存到队列中,队列为空,说明所有渡河条件的格子都已经遍历了,结果就是队列中存储过的格子数。

代码

class Solution {
    //使用广度优先遍历搜索
    public int movingCount(int m, int n, int k) {
        Queue<int []> queue = new LinkedList<>();
        queue.offer(new int[]{0, 0, 0, 0});
        int[] p = new int[4];
        boolean[][] visited = new boolean[m][n];
        int i = 0, j = 0, si = 0, sj = 0;
        int res = 0;
        while(!queue.isEmpty()){
            p = queue.poll();
            i = p[0]; 
            j = p[1]; 
            si = p[2];
            sj = p[3];
            //如果从队列中取出的点越界或者数位和大于k或者已经访问过,直接进行下次循环
            if(i == m || j == n || si + sj > k || visited[i][j] == true) continue;

            //如果从队列中取出的点符合要求,res++,并且将这个点的下面的点和右边的点存到队列中
            res++;
            visited[i][j] = true;
            queue.offer(new int[]{i + 1, j, (i + 1) % 10 == 0 ? si -8 : si + 1, sj});
            queue.offer(new int[]{i, j + 1, si, (j + 1) % 10 == 0 ? sj - 8 : sj + 1});
            
        }
        return res;
    }
}

参考:Krahets大佬题解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章