代码随想录——钥匙和房间(图论)

题目

有 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:

输入: [[1],[2],[3],[]] 输出: true 解释: 我们从 0 号房间开始,拿到钥匙 1。 之后我们去 1 号房间,拿到钥匙
2。 然后我们去 2 号房间,拿到钥匙 3。 最后我们去了 3 号房间。 由于我们能够进入每个房间,我们返回 true。 示例 2:

输入:[[1,3],[3,0,1],[2],[0]] 输出:false 解释:我们不能进入 2 号房间

思路

DFS和BFS的区别

DFS(深度优先搜索): dfs是朝一个方向去搜,不到黄河不回头,直到遇到绝境了,搜不下去了,再换方向(换方向的过程就是回溯的过程)

BFS(广度优先搜索):bfs是先把本节点所连接的所有节点遍历一遍,走到下一个节点的时候,再把连接节点的所有节点遍历一遍,搜索方向更像是发散,四面八方的搜索过程。

总结:

  • 广搜(bfs)是一圈一圈的搜索过程,广搜队列
  • 深搜(dfs)是一条路跑到黑然后再回溯,深度递归

DFS实现代码:

因为dfs朝着一个方向搜索,并且需要回溯,所以使用递归来实现最方便的

一般情况,深搜需要二维数组数组结构保存所有路径(结果集),需要一维数组保存单一路径(单条结果

dfs代码框架和回溯代码框架几乎差不多,如下:

void dfs(参数){
	if(终止条件){
		存放结果;
		return;
	}
	
	for(选择:本节点所连接的其他节点){
		处理节点;
		dfs(图,选择的节点);//递归
		(回溯,撤销处理结果);//根据题意,是否需要还原节点,即回溯
	}
}

BFS实现代码:

广搜适合解决两个点之间的最短路径问题

因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路(正是因为BFS一圈一圈的遍历方式,所以一旦遇到终止点,那么一定是一条最短路径),即只要BFS搜到终点一定是一条最短路径

当然,也有一些问题是广搜和深搜都可以解决的,例如岛屿问题,这类问题的特征就是不涉及具体的遍历方式,只要能把相邻且相同属性的节点标记上就行。

注意:BFS实现的容器并非只能用队列实现,bfs只是仅仅需要一个容器,能保存要遍历过的元素就可以,用队列,还是用栈,甚至用数组,都是可以的。

  • 用队列的话,就是保证每一圈都是一个方向去转,例如统一顺时针或者逆时针;因为队列是先进先出,加入元素和弹出元素的顺序是没有改变的。
  • 用栈的话,可能就是第一圈顺时针遍历,第二圈逆时针遍历,第三圈又顺时针遍历;因为栈是先进后出,加入元素和弹出元素的顺序改变了。但是广搜不需要注意转圈搜索的顺序吗

所以用队列,还是用栈都是可以的,只是大家都习惯用队列(FIFO先进先出)了,只不过要清楚,BFS并不是非要用队列,用栈也可以

bfs代码框架:

void bfs(){
	初始化队列Q;
	Q = {起始节点s};
	标记s已访问;
	while(que非空){Q队首元素u;
		u出队;
		if(u == 目标状态){
			...
		}
		所有与u相邻且未被访问的点进入队列;
		标记u为已访问;
	}
}

最后回到本题,本题给出的其实是一个有向图,对于题中的示例[[1],[2],[3],[]] [[1,3],[3,0,1],[2],[0]],画成对应的图为:

在这里插入图片描述
在这里插入图片描述

当 x 号房间中有 y 号房间的钥匙时,就可以从 x 号房间去往 y 号房间。如果将这 n 个房间看成有向图中的 n 个节点,那么上述关系就可以看作是图中的 x 号点到 y号点的一条有向边。这样一来,问题就变成了给定一张有向图,询问从 0 号节点出发是否能够到达所有的节点

所以本题是一个有向图搜索全路径的问题, 只能用深搜(DFS)或者广搜(BFS)来搜。

方法一:DFS

使用深度优先搜索的方式遍历整张图,统计可以到达的节点个数,并利用数组 vis 标记当前节点是否访问过,以防止重复访问

java代码如下:

class Solution {
	//一定要定义成全局变量,否则只能一个函数调用
	int num;//记录能够到达的房间数
	boolean[] vis;//判断是否去过该房间
	
	public boolean canVisitAllRooms(List<List<Integer>> rooms){
		int n = rooms.size();
		num = 0;
		vis = new boolean[n];
		dfs(rooms,0);
		return n == num;
	}
	
	public void dfs(List<List<Integer>> rooms, int x){
		vis[x] = true;
		num++;
		for(int u : rooms.get(x)){//这里get(x)取出的是一个列表,表示的是当前房间的钥匙,即可以去往哪些房间
			if(!vis[u]){//如果u未被访问过,即没去过这个房间
				dfs(rooms,u);
			}
		}
	}
}

方法二:BFS

使用广度优先搜索的方式遍历整张图,统计可以到达的节点个数,并利用数组 vis 标记当前节点是否访问过,以防止重复访问

class Solution {
	public boolean canVisitAllRooms(List<List<Integer>> rooms){
		int n = rooms.size();
		int num = 0;//记录可以到达的节点个数
		boolean[] vis = new boolean[n];
		Queue<Integer> que = new LinkedList<Integer>();
		que.offer(0);
		vis[0] = true;
		while(!que.isEmpty()){
			int x = que.poll();
			num++;
			for(int u : rooms.get(x)){//这里get(x)取出的是一个列表,表示的是当前房间的钥匙,即可以去往哪些房间
				if(!vis[u]){//如果u未被访问过,即没去过这个房间
					que.offer(u);
					vis[u] = true;
				}
			}
		}
		return num == n;
	}
}
  • 时间复杂度:O(n+m),其中 n 是房间的数量,m 是所有房间中的钥匙数量的总数
  • 空间复杂度:O(n),其中 n 是房间的数量,主要为队列的开销
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HDU-五七小卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值