JAVA LeetCode#802 找到最终的安全状态

题目描述

在有向图中, 我们从某个节点和每个转向处开始, 沿着图的有向边走。 如果我们到达的节点是终点 (即它没有连出的有向边), 我们停止。

现在, 如果我们最后能走到终点,那么我们的起始节点是最终安全的。 更具体地说, 存在一个自然数 K,  无论选择从哪里开始行走, 我们走了不到 K 步后必能停止在一个终点。

哪些节点最终是安全的? 结果返回一个有序的数组。

该有向图有 N 个节点,标签为 0, 1, ..., N-1, 其中 N 是 graph 的节点数.  图以以下的形式给出: graph[i] 是节点 j 的一个列表,满足 (i, j) 是图的一条有向边。

示例:
输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]]
输出:[2,4,5,6]
这里是上图的示意图。

提示:


    graph 节点数不超过 10000.
    图的边数不会超过 32000.
    每个 graph[i] 被排序为不同的整数列表, 在区间 [0, graph.length - 1] 中选取。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-eventual-safe-states

 

 

思路过程

这题一共提交了5次,慢慢修改,最后12ms,在所有 Java 提交中击败了100.00% 的用户!

 

第一次提交:
这题最先是考虑如果形成环的话就不加入,用flag[i]进行标记是否已经访问,如果重复访问说明成环,不加入。嗯,测试没问题,提交,结果超时了

public static List<Integer> eventualSafeNodes(int[][] graph) {
        List<Integer> ans = new ArrayList<Integer>();//记录结果
        int n = graph.length;
        boolean[] flag = new boolean[n];
        for ( int i = 0; i < n; i++ ) {
        	if (!DFS(graph, i, flag)) ans.add(i);//DFS遍历
        	Arrays.fill(flag, false);//复位
        }
        return ans;
    }
	
 public static boolean DFS( int[][] graph, int start, boolean[] flag ) {
	if ( flag[start] ) return true;//循环
	flag[start] = true;
	for ( int next : graph[start] ) {
		if ( DFS(graph, next, flag) ) return true;//发生循环则返回
	}
	flag[start] = false;//回退
	return false;
}

第二次提交:
额。。。思考了一下,每次都得遍历环时间复杂度很高,于是加了个set记录已经是环的点,这样就不用重复遍历环,如果遍历到该点,则直接返回,提交,又超时。。。继续改!!!

第三次提交:
把安全点也用set记录,提交,哇,终于过了。362 ms, 在所有 Java 提交中击败了6.67% 的用户。。。额。。这么渣的吗

public static List<Integer> eventualSafeNodes(int[][] graph) {
        Set<Integer> set = new HashSet<Integer>();//标记成环的点
        Set<Integer> f = new HashSet<Integer>();//标记安全的点
        List<Integer> ans = new ArrayList<Integer>();//记录结果
        int n = graph.length;
        boolean[] flag = new boolean[n];
        for ( int i = 0; i < n; i++ ) {
        	if (!DFS(graph, i, flag, set, f)) ans.add(i);//DFS遍历
        	else set.add(i);
        	Arrays.fill(flag, false);
        }
        return ans;
    }
	
public static boolean DFS( int[][] graph, int start, boolean[] flag, Set set, Set f ) {
	if ( f.contains(start) ) return false;//如果是安全点
	if ( flag[start] || set.contains(start) ) return true;//以访问或是成环的点
	flag[start] = true;//标记以访问
	for ( int next : graph[start] ) {
		if ( DFS(graph, next, flag, set, f) ) {
			set.add(start);//成环
			return true;
		}
	}
	flag[start] = false;
	f.add(start);//不成环
	return false;
}

第四次提交:
看一下网上解题吧,用的拓扑排序?嗯,看一下资料,继续改,提交!77 ms, 在所有 Java 提交中击败了43.33% 的用户。。。
这不是正确解法?!什么鬼

public static List<Integer> eventualSafeNodes(int[][] graph) {
        LinkedList<Integer> Q = new LinkedList<Integer>();	//队列
        List<Integer> ans = new ArrayList<Integer>();	//存储结果
        ArrayList<ArrayList<Integer>> arrs = new ArrayList<ArrayList<Integer>>();//反向记录
        int n = graph.length;
        int[] out = new int[n];
        for ( int i = 0; i < n; i++ ) arrs.add(new ArrayList<Integer>());
        for ( int i = 0; i < graph.length; i++ ) {
        	out[i] += graph[i].length;//记录出度个数
        	if ( out[i] == 0 ) Q.add(i);	//加入队列
        	for ( int j = 0; j < graph[i].length; j++ ) {
        		arrs.get(graph[i][j]).add(i);
        	}
        }
        
        while ( !Q.isEmpty() ) {
        	int index = Q.get(0);	//出队
        	Q.remove(0);
        	
        	for (int i : arrs.get(index)) {
				if ( --out[i] == 0 ) Q.add(i);	//减去出度,入队
			}
        }
        
        for ( int i = 0; i < out.length; i++ ) {
        	if ( out[i] == 0 ) ans.add(i);//安全点
        }
        return ans;
    }

复制其他代码,提交!15ms(╯‵□′)╯︵┻━┻

第五次提交:
仔细一看,这代码怎么跟我原先的思路差不多,研究研究,这不就是一模一样吗?只不过使用得很巧妙啊,直接用数组进行标记,嗯,再写一遍,提交,12 ms, 在所有 Java 提交中击败了100.00% 的用户,我的天啊,第一次啊

/*
* 0:未访问
* 1:以访问
* 2:安全
* 3:成环
*/
public static List<Integer> eventualSafeNodes(int[][] graph) {
   List<Integer> ans = new ArrayList<Integer>();//记录结果
   int n = graph.length;//长度
   int[] type = new int[n];//访问类型
   for ( int i = 0; i < n; i++ ) {
      	if ( DFS(graph, i, type) == 2 ) ans.add(i);
   }
   return ans;
}
	
public static int DFS( int[][] graph, int index, int[] type ) {
	if ( type[index] == 1 ) return 3;//如果访问过了,说明成环
	if ( type[index] != 0 ) return type[index];	//如果不是0,返回自身
	type[index] = 1;//标记访问了
	for (int i : graph[index]) {
		if ( DFS(graph, i, type) == 3 ) {
			type[i] = 3;
			return 3;
		}
	}
		
	type[index] = 2;//不成环
	return 2;
}

总结:难得的一次对题目进行深入研究,关键在于研究的过程!发现超时,那么用set记录下来,减少不必要的遍历,362ms。看题解,自己写一遍,77ms,不满意这个速度,继续研究。发现自己的思路还能这样使用,少了初始化flag,也节约了空间!12ms。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值