這道題本質還是搜索,因此可以使用深度優先搜索和廣度優先搜索進行解決。
原題
地上有一個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]
開始的,所以可以想象成根節點往子節點或兄弟節點的遍歷方式,深度優先搜索就是先遍歷子節點,子節點遍歷完成後,在遍歷兄弟節點。
終止條件應該有:
座標越界,也就是
x >= m
或者y >= n
。該點已經訪問過,既然訪問過,自然不用重新計算。
座標數字之和大於
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/
公衆號:健程之道