841. 鑰匙和房間

知識點:圖;遞迴

題目描述

有 N 個房間,開始時你位於 0 號房間。每個房間有不同的號碼:0,1,2,...,N-1,並且房間裡可能有一些鑰匙能使你進入下一個房間。

在形式上,對於每個房間 i 都有一個鑰匙列表 rooms[i],每個鑰匙 rooms[i][j] 由 [0,1,...,N-1] 中的一個整數表示,其中 N = rooms.length。 鑰匙 rooms[i][j] = v 可以開啟編號為 v 的房間。

最初,除 0 號房間外的其餘所有房間都被鎖住。

你可以自由地在房間之間來回走動。

如果能進入每個房間返回 true,否則返回 false。

示例
輸入: [[1],[2],[3],[]]
輸出: true
解釋:
我們從 0 號房間開始,拿到鑰匙 1。
之後我們去 1 號房間,拿到鑰匙 2。
然後我們去 2 號房間,拿到鑰匙 3。
最後我們去了 3 號房間。
由於我們能夠進入每個房間,我們返回 true。 輸入:[[1,3],[3,0,1],[2],[0]]
輸出:false
解釋:我們不能進入 2 號房間。

解法一:深度優先(DFS)

當 x 號房間中有 y 號房間的鑰匙時,我們就可以從 x 號房間去往 y 號房間。如果我們將這 n 個房間看成有向圖中的 n 個節點,那麼上述關係就可以看作是圖中的 x 號點到 y 號點的一條有向邊。

這樣一來,問題就變成了給定一張有向圖,詢問從 0 號節點出發是否能夠到達所有的節點。

深度優先的意思就是先進入第一個房,然後拿出第一把鑰匙,去開下一個房,然後再拿到這個房的第一個鑰匙,再去開下一個,直到沒鑰匙了,退回上一個房,重複以往。這是一個遞迴的過程,和二叉樹的遍歷一樣。

可以用一個數組來記錄到達過哪個房間,防止重複計算。

函式功能:遍歷房間

1.終止條件:把第0個房間的鑰匙拿完;

2.該做什麼:每個房間應該做什麼呢,判斷陣列標誌那個房間去過沒有,去過了就跳過,沒去過就進去,並把標誌位置為true。計數器加1.最後看是否等於房間個數。

class Solution {
boolean[] vis;
int num = 0;
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
int n = rooms.size();
vis = new boolean[n]; //設定第n個房間是否開啟過的標誌位;
canVisitAllRooms(rooms, 0);
return num == n; //檢視是否遍歷完;
}
private void canVisitAllRooms(List<List<Integer>> rooms, int roomnum){
vis[roomnum] = true;
num++;
for(int i : rooms.get(roomnum)){
if(!vis[i]){ //沒有訪問過就進去;
canVisitAllRooms(rooms, i);
}
}
}
}

解法二:廣度優先(BFS)

廣度優先的意思就是我們不拿到一個鑰匙走到頭了,我們一個房間一個房間的進,拿到第一個房間的鑰匙,把第一個房間裡的鑰匙對應的門都開啟,然後再去第二個房間。這樣一個一個的進。

class Solution {
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
int n = rooms.size();
boolean[] vis = new boolean[n]; //標誌位就是防止被重複遍歷;
int num = 0;
Queue<Integer> queue = new LinkedList<>();
vis[0] = true; //設定0號訪問過,為了防止節點多次入隊,需要在入隊前將其設定為已訪問;
queue.add(0); //0號房間入隊;
while(!queue.isEmpty()){
int x = queue.poll();
num++;
for(int i : rooms.get(x)){
if(!vis[i]){ //沒有被訪問過;
vis[i] = true;
queue.add(i); //保證入隊的都是沒有被遍歷過的;
}
}
}
return n == num;
}
}

體會

  • 1.只要是廣度優先的,肯定要用佇列,天然是在一起的。 一邊彈出,一邊遍歷,彈出一個以後,就把它的孩子或者是它的關聯入隊,保證把這一層,或這個節點的都彈出了,接著繼續彈下一層或者下一個節點的。
  • 2.樹的BFS:先把root節點入隊,然後再一層一層的遍歷。

    圖的BFS也是一樣的,與樹的BFS的區別是:

    • 1.樹只有一個root,而圖可以有多個源點,所有首先需要將多個源點入隊。
    • 2.樹是有向的因此不需要標誌是否訪問過,而對於無向圖而言,必須得標誌是否訪問過!並且為了防止某個節點多次入隊,需要在入隊前將其設定為已訪問!
  • 3.樹用DFS比較多,圖用BFS比較多;