841、难度中等:
题意:存在一个二维数组 rooms[i] [j],第一维的序号 i 代表房间序号。而每个房间里有一串钥匙,这也是数组 rooms 的每个元素是一个一维数组的原因,所以 rooms 是一个二维数组。
思路:当 x 号房间中有 y 号房间的钥匙时,我们就可以从 x 号房间去往 y 号房间。如果我们将这 n 个房间看成有向图中的 n 个节点,那么上述关系就可以看作是图中的 x 号点到 y 号点的一条有向边。
这样一来,问题就变成了给定一张有向图,询问从 0 号节点出发是否能够到达所有的节点。
方法一:深度优先搜索:
思路:使用深度优先搜索的方式遍历整张图,统计可以到达的节点个数,并利用数组 vis 标记当前节点是否访问过,以防止重复访问。
class Solution {
// 写在全局中,可以跨方法保存数据
boolean[] vis;
int num;
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
int n = rooms.size();
num = 0;
vis = new boolean[n];
dfs(rooms, 0);
return num == n;
}
public void dfs(List<List<Integer>> rooms, int x) {
// 把当前节点设置为true(解决了不管之前遇没遇到)
// 由于下方调用dfs自己是由条件的,这导致只有vis[x] = true;语句
// 只有在 x 确实没有遇到过时才会被执行
vis[x] = true;
num++;
// 遍历当前节点能到达的所有节点
for (int it : rooms.get(x)) {
// 如果下一个访问节点没有遇到过,那就调用自己
if (!vis[it]) {
dfs(rooms, it);
}
// 否则直接略过,检查下一个
}
}
}
方法二:广度优先搜索:
思路:使用广度优先搜索的方式遍历整张图,统计可以到达的节点个数,并利用数组 vis 标记当前节点是否访问过,以防止重复访问
原理:创建一个队列,先入队第一个元素,再创建一个记录序列,用于保存哪些值都被遍历过了(遍历过的是指以前进过队列里)。然后循环弹出队列里的东西,每个弹出的元素都可以引向新的一批元素,再从中选出不再记录序列里的东西加入到队列中同时再将这些东西写入到记录列表中。
由于存在一个限制元素入队列的判断语句,使得队列迟早会空,这就是循环结束条件。也因为这个判断语句,循环的次数就是遍历过的元素总次数。我们将这个次数和我们要比对的数比对作为整体返回值。
class Solution {
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
int n = rooms.size(), num = 0;
boolean[] vis = new boolean[n];
Queue<Integer> que = new LinkedList<Integer>();
vis[0] = true;
que.offer(0);
while (!que.isEmpty()) {
// 将当前节点弹出并准备遍历其所有指向节点
int x = que.poll();
num++;
// 遍历当前节点能指向的所有节点
for (int it : rooms.get(x)) {
// 只要该指向节点没有被走到过就将其纳入队列中
if (!vis[it]) {
vis[it] = true;
que.offer(it);
}
}
}
return num == n;
}
}
142、难度中等:
要求:使用 O(1)
空间解决此题
方法一:哈希表:时空复杂度O(N)
原理:我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode pos = head;
// 哈希表里的值是ListNode类型的
Set<ListNode> visited = new HashSet<ListNode>();
while (pos != null) {
if (visited.contains(pos)) {
return pos;
} else {
visited.add(pos);
}
pos = pos.next;
}
return null;
}
}
方法二:快慢指针:时间复杂度O(N) 空间复杂度O(1)
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
ListNode slow = head, fast = head;
while (fast != null) {
slow = slow.next;
if (fast.next != null) {
fast = fast.next.next;
// next指针可以到达null,不构成环,返回null
} else {
return null;
}
// 由公式a=c+(n−1)(b+c)可得
// slow节点从 b 点开始走,先走距离 c 然后再走 n-1 圈内环
// ptr节点从起点开始走,走距离 a 后到达入环点
// 二者距离正好相等
// 所以最终slow会和ptr在入环点相遇
if (fast == slow) {
ListNode ptr = head;
while (ptr != slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
}
}