深度 / 廣度優先算法題總結

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大佬題解

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