LeetCode Notes_#752 開啟轉盤鎖
LeetCode Notes_#752 開啟轉盤鎖
LeetCodeContents
題目
你有一個帶有四個圓形撥輪的轉盤鎖。每個撥輪都有10個數字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每個撥輪可以自由旋轉:例如把 '9' 變為 '0','0' 變為 '9' 。每次旋轉都只能旋轉一個撥輪的一位數字。
鎖的初始數字為 '0000' ,一個代表四個撥輪的數字的字串。
列表 deadends 包含了一組死亡數字,一旦撥輪的數字和列表裡的任何一個元素相同,這個鎖將會被永久鎖定,無法再被旋轉。
字串 target 代表可以解鎖的數字,你需要給出最小的旋轉次數,如果無論如何不能解鎖,返回 -1。
示例 1:
輸入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
輸出:6
解釋:
可能的移動序列為 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 這樣的序列是不能解鎖的,
因為當撥動到 "0102" 時這個鎖就會被鎖定。
提示:
- 死亡列表 deadends 的長度範圍為 [1, 500]。
- 目標數字 target 不會在 deadends 之中。
- 每個 deadends 和 target 中的字串的數字會在 10,000 個可能的情況 '0000' 到 '9999' 中產生。
思路分析
題目描述的是一個很具體的問題,但是乍一看是沒有什麼思路的,需要將問題轉化,將問題抽象成一個狀態轉移的問題。
將0000~9999
看作不同的狀態,這些狀態之間可以互相轉換。
轉換的條件是,兩個數字僅有一位相差1(0和9也算在內)。
按照上述描述,可以畫出一個狀態轉移圖,圖中有0000~9999
這些節點,滿足狀態轉移條件的節點之間有邊連線。最小旋轉次數就等於從起始狀態0000
到target
的最短路徑。
求最短路徑的一個方法就是廣度優先遍歷BFS,廣度優先遍歷也就是層序遍歷,從0000
到target
的過程中,廣度優先遍歷的層數就是最短路徑。具體寫法類似於樹的BFS。
需要注意的點:
- 狀態轉移的過程不可以出現
deadends
- BFS過程中,可能出現重複訪問的節點,所以還需要判斷重複,如果是重複的節點就不入隊
- 需要使用
null
作為層與層之間的分隔符,否則不好計數
解答
class Solution {
public int openLock(String[] deadends, String target) {
//兩個HashSet,用於判斷重複
HashSet<String> dead = new HashSet<>();
for(String str:deadends){
dead.add(str);
}
HashSet<String> seen = new HashSet<>();
//建立BFS用到的佇列,先把遍歷的起點‘0000’入隊
Queue<String> queue = new LinkedList<>();
queue.offer("0000");
//第一層只有只有一個節點,所以加入分隔符
queue.offer(null);
//計數器,記錄路徑的長度
int depth = 0;
while(!queue.isEmpty()){
String cur = queue.poll();
//null是層與層之間的分隔符
if(cur == null){
//遇到null說明接下來的元素就是新的一層了,depth增加1
depth++;
//判斷下一層是否還有元素,如果有,就要新增null到隊尾(因為這時下一層已經全部入隊)
//這時新增到隊尾,剛好就可以分隔開下一層與下下層
if(queue.peek() != null)
queue.offer(null);
}else if(cur.equals(target)){
return depth;
//如果cur在dead當中,就不能從這個節點走,在迴圈中不做任何操作,直接進入下一輪迴圈
//否則,就把cur的所有鄰居節點入隊,等待遍歷
}else if(!dead.contains(cur)){
//外層迴圈:4位數
for(int i = 0;i <= 3;i++){
//內層迴圈:加1或者減1(加-1)
for(int d = -1;d <= 1;d += 2){
//修改的那一位數字,記為newDigit
//求餘是為了符合0,9之間的轉換
int newDigit = ((cur.charAt(i) - '0') + d + 10) % 10;
//將newDigit拼接到cur字串的合適位置
String StringNeibor = cur.substring(0,i) + newDigit + cur.substring(i+1);
//對於每個鄰居節點,需要判斷是否是已經訪問過的,如果已經訪問過,就不再入隊
if(!seen.contains(StringNeibor)){
seen.add(StringNeibor);
queue.offer(StringNeibor);
}
}
}
}
}
//最後沒找到,就返回-1
return -1;
}
}
複雜度分析
時間複雜度:O(N2 * AN + D)。我們用 A 表示數字的個數,N 表示狀態的位數,D表示陣列 deadends 的大小。在最壞情況下,我們需要搜尋完所有狀態,狀態的總數為 O(AN)。對於每個狀態,我們要列舉修改的位置,需要 O(N) 的時間,列舉後得到新的狀態同樣需要 O(N) 的時間。
空間複雜度:O(AN + D)),用來儲存佇列以及 deadends 的集合。