We are given a 2-dimensional `grid`. `"."` is an empty cell, `"#"` is a wall, `"@"` is the starting point, (`"a"`, `"b"`, ...) are keys, and (`"A"`, `"B"`, ...) are locks.
We start at the starting point, and one move consists of walking one space in one of the 4 cardinal directions. We cannot walk outside the grid, or walk into a wall. If we walk over a key, we pick it up. We can't walk over a lock unless we have the corresponding key.
For some 1 <= K <= 6, there is exactly one lowercase and one uppercase letter of the first K
letters of the English alphabet in the grid. This means that there is exactly one key for each lock, and one lock for each key; and also that the letters used to represent the keys and locks were chosen in the same order as the English alphabet.
Return the lowest number of moves to acquire all keys. If it's impossible, return -1
.
Example 1:
Input: ["@.a.#","###.#","b.A.B"]
Output: 8
Example 2:
Input: ["@..aA","..B#.","....b"]
Output: 6
Note:
1 <= grid.length <= 30
1 <= grid[0].length <= 30
grid[i][j]
contains only'.'
,'#'
,'@'
,'a'-``'f``'
and'A'-'F'
- The number of keys is in
[1, 6]
. Each key has a different letter and opens exactly one lock.
這道題給了我們一個迷宮,其中的點表示可通過的位置,井號表示牆,不能通過。小寫字母表示鑰匙,大寫字母表示門,只有拿到了對應的鑰匙才能通過門的地方,有點紅白機游戲的感覺。問我們收集到所有的鑰匙需要的最小步數,當無法收到所有鑰匙的時候,返回 -1。對於迷宮遍歷找最小步數的題,應該並不陌生,基本都是用 BFS 來解的。這里雖然沒有一個固定的終點位置,但也是有終止條件的,那就是收集到所有的鑰匙。這道題好就好在對鑰匙進行了一些限定,最多有6把,最少有1把,而且都是按字母順序出現的,就是說只有一把鑰匙的時候,一定是a,兩把的話一定是a和b。我們需要保存所有當前已經獲得的鑰匙,並且還要隨時查詢是否已經獲得了某個特定的鑰匙,還需要查詢是否已經獲得了所有的鑰匙。由於之前說了知道了鑰匙的個數,就能確定是哪些鑰匙,這樣就可以對鑰匙進行編號,鑰匙a編為0,同理,b,c,d,e,f 分別編為 1,2,3,4,5。最簡單的實現就是用一個長度為k的 boolean 數組,獲得了某個鑰匙就標記為 true,查詢某個鑰匙是否存在就直接在數組中對應位置查詢即可,判斷是否獲得所有鑰匙就線性遍歷一下數組即可,由於最多就6把鑰匙,所以遍歷也很快。當然,若我們想秀一波技巧,也可以將鑰匙編碼成二進制數,對應位上的0和1表示該鑰匙是存在,比如二進制數 111111 就表示六把鑰匙都有了,而 100111 就表示有鑰匙 a,d,e 和f,這樣查詢某個鑰匙或查詢所有鑰匙的時間復雜度都是常數級了,既省了空間又省了時間,豈不美哉?!
分析到這里,基本上難點都 cover 了,可以准備寫代碼了。整體框架還是經典的 BFS 寫法,再稍加改動即可。這里的隊列 queue 不能只放位置信息,還需要放當前的鑰匙信息,因為到達不同的位置獲得的鑰匙個數可能是不同的,二維的位置信息編碼成一個整數,再加上鑰匙的整數,組成一個 pair 對兒放到隊列中。由於參數中沒有事先告訴我們起點的位置,所以需要先遍歷一遍整個迷宮,找到@符號,將位置和當前鑰匙信息加入到 queue 中。為了避免死循環,BFS 遍歷是需要記錄已經訪問過的位置的,這里的狀態當然也要加入當前鑰匙的信息,為了簡單起見,將其編碼成一個字符串,前半部分放位置編碼成的整數,中間加個下划線,后面放鑰匙信息的整數,組成的字符串放到 HashSet 中即可。遍歷的過程中同時還要統計鑰匙的個數,有了總個數 keyCnt,就能知道拿到所有鑰匙后編碼成的整數。在 while 循環,采用層序遍歷的機制,對於同一層的結點,分別取出位置信息和鑰匙信息,此時先判斷下是否已經拿到所有鑰匙了,是的話直接返回當前步數 res。否則就要檢測其四個相鄰位置,需要注意的是,對於每個相鄰位置,一定要重新取出之前的鑰匙信息,否則一旦鑰匙信息修改了而沒有重置的話,直接到同一層的其他結點可能會引起錯誤。取出的鄰居結點的位置要先判斷是否越界,還要判斷是否為牆,是的話就直接跳過。若是門的話,要看當前是否有該門對應的鑰匙,有的話才能通過。若遇到了鑰匙,則需要修改鑰匙信息。這些都完成了之后,將當前的位置和鑰匙信息編碼成一個字符串,看 HashSet 是否已經有了這個狀態,沒有的話,則加入 HashSet,並同時加入 queue,每當一層結點遍歷完成后,結果 res 自增1即可,參見代碼如下:
``` class Solution { public: int shortestPathAllKeys(vector
Github 同步地址:
https://github.com/grandyang/leetcode/issues/864
參考資料:
https://leetcode.com/problems/shortest-path-to-get-all-keys/
https://leetcode.com/problems/shortest-path-to-get-all-keys/discuss/146878/Java-BFS-Solution
[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)