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.html
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.git
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.github
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:less
Input: ["@..aA","..B#.","....b"] Output: 6
Note:ide
1 <= grid.length <= 30
1 <= grid[0].length <= 30
grid[i][j]
contains only '.'
, '#'
, '@'
, 'a'-``'f``'
and 'A'-'F'
[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,這樣查詢某個鑰匙或查詢全部鑰匙的時間複雜度都是常數級了,既省了空間又省了時間,豈不美哉?!ui
分析到這裏,基本上難點都 cover 了,能夠準備寫代碼了。總體框架仍是經典的 BFS 寫法,再稍加改動便可。這裏的隊列 queue 不能只放位置信息,還須要放當前的鑰匙信息,由於到達不一樣的位置得到的鑰匙個數多是不一樣的,二維的位置信息編碼成一個整數,再加上鑰匙的整數,組成一個 pair 對兒放到隊列中。因爲參數中沒有事先告訴咱們起點的位置,因此須要先遍歷一遍整個迷宮,找到@符號,將位置和當前鑰匙信息加入到 queue 中。爲了不死循環,BFS 遍歷是須要記錄已經訪問過的位置的,這裏的狀態固然也要加入當前鑰匙的信息,爲了簡單起見,將其編碼成一個字符串,前半部分放位置編碼成的整數,中間加個下劃線,後面放鑰匙信息的整數,組成的字符串放到 HashSet 中便可。遍歷的過程當中同時還要統計鑰匙的個數,有了總個數 keyCnt,就能知道拿到全部鑰匙後編碼成的整數。在 while 循環,採用層序遍歷的機制,對於同一層的結點,分別取出位置信息和鑰匙信息,此時先判斷下是否已經拿到全部鑰匙了,是的話直接返回當前步數 res。不然就要檢測其四個相鄰位置,須要注意的是,對於每一個相鄰位置,必定要從新取出以前的鑰匙信息,不然一旦鑰匙信息修改了而沒有重置的話,直接到同一層的其餘結點可能會引發錯誤。取出的鄰居結點的位置要先判斷是否越界,還要判斷是否爲牆,是的話就直接跳過。如果門的話,要看當前是否有該門對應的鑰匙,有的話才能經過。若遇到了鑰匙,則須要修改鑰匙信息。這些都完成了以後,將當前的位置和鑰匙信息編碼成一個字符串,看 HashSet 是否已經有了這個狀態,沒有的話,則加入 HashSet,並同時加入 queue,每當一層結點遍歷完成後,結果 res 自增1便可,參見代碼以下:編碼
class Solution { public: int shortestPathAllKeys(vector<string>& grid) { int m = grid.size(), n = grid[0].size(), keyCnt = 0, res = 0; queue<pair<int, int>> q; unordered_set<string> visited; vector<int> dirX{-1, 0, 1, 0}, dirY{0, 1, 0, -1}; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == '@') { q.push({i * n + j, 0}); visited.insert(to_string(i * n + j) + "_0"); } if (grid[i][j] >= 'a' && grid[i][j] <= 'f') ++keyCnt; } } while (!q.empty()) { for (int i = q.size(); i > 0; --i) { int t = q.front().first, curKeys = q.front().second; q.pop(); if (curKeys == (1 << keyCnt) - 1) return res; for (int k = 0; k < 4; ++k) { int x = t / n + dirX[k], y = t % n + dirY[k], keys = curKeys; if (x < 0 || x >= m || y < 0 || y >= n) continue; char c = grid[x][y]; if (c == '#') continue; if (c >= 'A' && c <= 'F' && ((keys >> (c - 'A')) & 1) == 0) continue; if (c >= 'a' && c <= 'f') keys |= 1 << (c - 'a'); string str = to_string(x * n + y) + "_" + to_string(keys); if (!visited.count(str)) { visited.insert(str); q.push({x * n + y, keys}); } } } ++res; } return -1; } };
Github 同步地址:spa
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