劍指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

原題url:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/

解題

深度優先搜索

從一個點出發,遍歷完所有點,爲了保證不會重複遍歷,因此我們可以藉助一個二維矩陣記錄已經遍歷過的點。而用深度優先搜索遍歷的話,一般都是使用遞歸的。

需要注意的是,雖然機器人可以上下左右移動,但因爲是從[0, 0]開始的,所以可以想象成根節點往子節點或兄弟節點的遍歷方式,深度優先搜索就是先遍歷子節點,子節點遍歷完成後,在遍歷兄弟節點。

終止條件應該有:

  1. 座標越界,也就是 x >= m 或者 y >= n 。

  2. 該點已經訪問過,既然訪問過,自然不用重新計算。

  3. 座標數字之和大於 k

求數字各數位之和,最簡單的方法應該就是 摸除% + 整除/ 就可以了。

我們來看看代碼:

class Solution {
    public int movingCount(int m, int n, int k) {
        int result = 0;
        int max = m > n ? m : n;
        // key爲數字,value爲該數字各位之和
        Map<Integer, Integer> numMap = new HashMap<>(max * 4 / 3 + 1);
        // 記錄已經訪問過的節點
        boolean[][] visited = new boolean[m][n];
        // 從(0, 0)開始移動
        return move(0, 0, m, n, k, numMap, visited);
    }

    public int move(int x, int y, int m, int n, int k, Map<Integer, Integer> numMap, boolean[][] visited) {
        // 是否越界
        if (x >= m || y >= n) {
            return 0;
        }

        // 如果該節點已經訪問過
        if (visited[x][y] == true) {
            // 說明該方格所代表的次數已經被計算過,因此返回0
            return 0;
        }

        // 標記該節點已經訪問過
        visited[x][y] = true;
        // 計算
        int xSum = getNumSum(x, numMap);
        int ySum = getNumSum(y, numMap);
        if (xSum + ySum > k) {
            return 0;
        }

        // 嘗試向下、向右
        return 1 + move(x + 1, y, m, n, k, numMap, visited) + move(x, y + 1, m, n, k, numMap, visited);
    }

    public int getNumSum(int num, Map<Integer, Integer> numMap) {
        Integer sum = numMap.get(num);
        if (sum != null) {
            return sum;
        }

        int key = num;
        sum = 0;
        while (num != 0) {
            sum += num % 10;
            num = num / 10;
        }
        numMap.put(key, sum);
        return sum;
    }
}

提交OK。我們來看看這種方法的複雜度:

  • 時間複雜度 O(MN) :最差情況下,機器人遍歷矩陣所有單元格,此時時間複雜度爲 O(MN)。

  • 空間複雜度 O(MN) :visited 矩陣的大小就是 mn,因此使用了 O(MN) 的額外空間。

廣度優先搜索

廣度優先搜索,也就是從根節點出發,先遍歷兄弟節點,再遍歷子節點。一般我們都需要藉助一個隊列存儲已經即將要遍歷的節點,因爲隊列的特性是先進先出,因此當父節點遍歷完成後,會依序遍歷所有該父節點的所有子節點(這些節點都是兄弟),再遍歷下一層的子節點。

(PS:現在想想,如果用棧存儲已經遍歷過的節點,也是可以的,只是訪問節點的方式並沒有什麼規律可言。)

針對該機器人的運動,也是從 [0, 0] 出發,向下向右移動,層層遞進。

接下來我們看看代碼:

class Solution {
    public int movingCount(int m, int n, int k) {
        int result = 0;
        int max = m > n ? m : n;
        // key爲數字,value爲該數字各位之和
        Map<Integer, Integer> numMap = new HashMap<>(max * 4 / 3 + 1);
        // 記錄已經訪問過的節點
        boolean[][] visited = new boolean[m][n];
        // 記錄還未訪問結束的點
        Queue<Coordinate> queue = new LinkedList<>();
        // 從(0, 0)開始移動
        queue.offer(new Coordinate(0, 0));
        while (!queue.isEmpty()) {
            // 獲取隊首元素
            Coordinate coordinate = queue.poll();
            // 判斷當前座標是否有效
            if (coordinate.x >= m || coordinate.y >= n) {
                continue;
            }
            // 判斷當前左邊是否已經訪問過
            if (visited[coordinate.x][coordinate.y]) {
                continue;
            }
            // 標記當前座標已經訪問過
            visited[coordinate.x][coordinate.y] = true;
            // 判斷當前座標是否有效
            int xSum = getNumSum(coordinate.x, numMap);
            int ySum = getNumSum(coordinate.y, numMap);
            if (xSum + ySum > k) {
                continue;
            }
            // 如果有效
            result++;
            // 將下邊一格節點放入隊列中
            queue.add(new Coordinate(coordinate.x + 1, coordinate.y));
            // 將右邊一格節點放入隊列中
            queue.add(new Coordinate(coordinate.x, coordinate.y + 1));
        }
        return result;
    }

    public int getNumSum(int num, Map<Integer, Integer> numMap) {
        Integer sum = numMap.get(num);
        if (sum != null) {
            return sum;
        }

        int key = num;
        sum = 0;
        while (num != 0) {
            sum += num % 10;
            num = num / 10;
        }
        numMap.put(key, sum);
        return sum;
    }

    class Coordinate {
        int x;
        int y;

        public Coordinate(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

提交OK。我們來看看這種方法的複雜度:

  • 時間複雜度 O(MN) :最差情況下,機器人遍歷矩陣所有單元格,此時時間複雜度爲 O(MN)。

  • 空間複雜度 O(MN) :visited 矩陣的大小就是 mn,因此使用了 O(MN) 的額外空間。

既然上下兩種方法時間複雜度相同,但比較奇怪的在於,我在力扣上提交時,上面一種方法所花費的時間是 1ms ,但這種方法所花費的時間是 7ms 。既然複雜度的計算是忽略了係數、低階、常數,但我認爲上下兩種方法即使不忽略,應該也是一樣的。 如果你有新的看法,歡迎指教。

求座標之和

求座標之和的方法,我寫的是比較簡單的一種,但如果你好好想想,就可以發現有更簡單的方法。

正常情況下,隨着數字逐漸變大,數字各位之和應該也是逐漸上升的,但唯一的特殊情況就是 進位 ,比如 19 變到 20 ,各數位之和從 10 變爲 2,其實細心點就可以發現,十位數雖然加1,但個位數減9,因此總體減8。所以可以總結出:

假設現在的數字爲x,其各位數之和爲Sum(x),那麼下一個Sum(x + 1)爲:
Sum(x + 1) = ((x + 1) % 10 == 0) ? (Sum(x) - 8) : (Sum(x) + 1)

那麼上面的代碼還有可以優化的地方,這個就留給大家去完成了。

總結

以上就是這道題目我的解答過程了,不知道大家是否理解了。這道題本質還是搜索,因此可以使用深度優先搜索和廣度優先搜索進行解決。

有興趣的話可以訪問我的博客或者關注我的公衆號,說不定會有意外的驚喜。

https://death00.github.io/

公衆號:健程之道

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