LeetCode – 802. Find Eventual Safe States

In a directed graph, we start at some node and every turn, walk along a directed edge of the graph.  If we reach a node that is terminal (that is, it has no outgoing directed edges), we stop.

Now, say our starting node is eventually safe if and only if we must eventually walk to a terminal node.  More specifically, there exists a natural number K so that for any choice of where to walk, we must have stopped at a terminal node in less than K steps.

Which nodes are eventually safe?  Return them as an array in sorted order.

The directed graph has N nodes with labels 0, 1, ..., N-1, where N is the length of graph.  The graph is given in the following form: graph[i] is a list of labels j such that (i, j) is a directed edge of the graph.

Example:
Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Output: [2,4,5,6]
Here is a diagram of the above graph.

Illustration of graph

Note:

  • graph will have length at most 10000.
  • The number of edges in the graph will not exceed 32000.
  • Each graph[i] will be a sorted list of different integers, chosen within the range [0, graph.length - 1].

题意:

在一个有向图里,如果一个节点没有出去到其他节点的边,那么这个节点就是一个终点。如果从一个节点出发经过有限的步数最终一定能够到达一个终点,那么该节点就是最终安全的节点。给你一个有向图,如何找出图中所有最终安全的节点?

假设有向图里有N个节点,分别用序号0, 1, …, N-1表示。该图用一个数组graph表示,其中每个graph[i]是一个表示节点序号j的列表,表明图中存在一个从节点i到节点j的边。

例如,如果输入graph = [[1,2],[2,3],[5],[0],[5],[],[]],则对应的有向图如下图所示。图中最终安全的节点为[2, 4, 5, 6]。

分析:

解法一:剔除到终点的边

首先,如果一个点本身就是终点(即没有出去的边),那么该节点就是最终安全的。在示例的图中,节点5和节点6都是终点,因此它们是最终安全的。

另外,如果一个节点所有出去的边都是走向终点的,那么该节点也是最终安全的,因为从该节点出发不会进入一个环。因此,我们找出所有出度为0的节点(开始只有终点是出去为0的节点)。接下来,把进入最终安全的节点的边剔除。剔除边之后如果发现有新的节点的出度变成0,那么该节点也是最终安全的。接下来再剔除进入它们的边。

前面我们已经知道节点5是最终安全的。有两条边进入节点5,分别来至节点2和节点4。在剔除这两条边之后,节点2和节点4的出度都是0,因此它们都是最终安全的。

如果所有进入最终安全的边都剔除后哪个节点仍然还有边出去,那么这些边将进入环。这样的节点不是最终安全的。

在示例图中,在剔除两条进入节点2的边(分别来自节点0和节点1)之后,再也没有其他能够进入最终安全的节点的边了。我们发现剩下3条边,第一条从节点0出发进入节点1,第二条从节点1出发进入节点3,第三条从节点3出发进入节点0。它们形成了一个环,因此它们都不是最终安全的节点。

基于这个思路,我们可以写出下面的代码:

public List<Integer> eventualSafeNodes(int[][] graph){
	List<Set<Integer>> ins = new ArrayList<>();
	int[] outCounts = new int[graph.length];
	for (int i = 0; i < graph.length; i ++) {
		outCounts[i] = graph[i].length;
		ins.add(new HashSet<Integer>());
	}
	
	for (int i = 0; i < graph.length; i ++) {
		for (int j : graph[i]) {
			ins.get(j).add(i);
		}
	}
	
	Queue<Integer> noOuts = new LinkedList<>();
	for (int i = 0; i < outCounts.length; i ++) {
		if (outCounts[i] == 0) {
			noOuts.offer(i);
		}
	}
	
	while (!noOuts.isEmpty()) {
		int noOut = noOuts.poll();
		for (int in : ins.get(noOut)) {
			if (-- outCounts[in] == 0) {
				noOuts.offer(in);
			}
		}
	}
	
	List<Integer> res = new ArrayList<>();
	for (int i = 0; i < outCounts.length; i ++) {
		if (outCounts[i] == 0) {
			res.add(i);
		}
	}
	return res;
}

在上述代码中,数组outCounts记录每个节点出度。如果一个节点的出度变成0,它就是最终安全的。队列noOuts用来存放已知的出度为0的节点。我们用动态数组ins记录进入每个节点的所有边的起始节点。在发现一个节点是最终安全的之后,我们剔除进入该节点的所有边,这些边的起始节点的出度减一。如果一个节点的出度变成0,则放入队列noOuts中,并继续剔除进入该节点的边。

这个过程实际上是广度优先搜索。

解法二:深度优先搜索

我们首先要理解题目中最终安全的节点这个概念。如果一个节点处在一个环中或者有可能进入环,那么我们不能保证一定能够到达某个终点,因此也就不是最终安全的节点。

在例子中的有向图中,存在一个由节点0、1、3组成的环,因此这3个节点不是最终安全的节点。其他的几个节点2、4、5、6是最终安全的节点。

我们可以用深度优先搜索算法去判断一条路径是不是含有环。我们在用深度优先搜索算法的时候,如果一条路径的下一个节点是之前已经搜索过的节点,那么该路径中包含环。路径中进入环之前的节点以及环里的所有节点都不是最终安全的。

我们可以基于深度优先搜索写出如下代码:

public List<Integer> eventualSafeNodes1(int[][] graph){
	int[] nodeStatus = new int[graph.length];
	for (int i = 0; i < graph.length; i ++) {
		checkNodeStatus(graph, i, nodeStatus);
	}
	
	List<Integer> res = new ArrayList<>();
	for (int j = 0; j < nodeStatus.length; j ++) {
		if (nodeStatus[j] == 1) {
			res.add(j);
		}
	}
	return res;
}

public void checkNodeStatus(int[][] graph, int index, int[] nodeStatus) {
	if (nodeStatus[index] == 0) {
		nodeStatus[index] = 2;
		for (int i : graph[index]) {
			checkNodeStatus(graph, i, nodeStatus);
			if(nodeStatus[i] == 2) {
				return ;
			}
		}
		nodeStatus[index] = 1;
	}
}

在上述代码中,数组states记录每个节点的状态。如果一个节点从来没有被遍历过,那么它的状态是0。一旦我们用深度优先搜索算法遍历到一个节点,我们就知道它是最终安全的(状态值为1)还是在某个环里或是会进入到某个环里(状态值为2)。

我们顺序扫描每个节点。如果一个节点之前没有被遍历过,则从该节点出发用深度优先搜索算法遍历,这就是checkNodeState的功能。如果一个节点有一条出去的边可能进入到环,那么该节点就不是最终安全的。一个节点只有所有出去的边都能最终到达终点节点,该节点才是最终安全的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值