https://leetcode.com/problems/redundant-connection-ii/
这题做下来总觉得不够hard模式,因为其主要逻辑几乎就在一些简单的几何关系中。
画了下图,自己总结了一下所谓多连一条有向边会导致哪些情况。粗略分为两类:
1,指向的那个节点是自己的直系祖先,这样会构成环
2, 指向的那个节点不是自己的直系祖先,这样会造成简单的“双父”的问题
当然也有这两种情况兼有的情形,所以我的思路是先检查是否存在双父问题,如果存在,则找出对应哪两条边(没错,如果存在双父问题,则有且只有两条),得到双父的边集合A, 再检查有向环路问题,把构成环路的所有边列举出来,构成集合B。
上面两个集合为方便起见,其元素都用边数组的下标作为元素即可。
如果集合A和集合B都存在,则取二者的交集,在得到的交集中取最大者
如果仅有A存在,则取A中的最大者
如果仅有B存在,则取B中的最大者。
思路大概就是这样,至于怎么找有没有环的细节,倒是很简单的事,直接从某边开始,查其的下一条的下一条下一条。。。。直到碰到重复边或者不存在下一条边为止。
找双父也很简单,只要看每个边的第二个元素,在给的边数组中有没有重复(既在多个下标处都出现过)即可,有就说明有双父,没有就说明没有双父。
辅助的数据结构用了两个map,分别记录左顶点出现的下标集合和右顶点出现的下标集合。
代码如下:
package com.example.demo.leetcode;
import java.util.*;
public class RedundantConnectionII {
public int[] findRedundantDirectedConnection(int[][] edges) {
// turn edges into 2 maps:
// 1: number with leftNode map
// 2: number with rightNode map
Map<Integer, Set<Integer>> left = new HashMap<>();
Map<Integer, Set<Integer>> right = new HashMap<>();
boolean doubleRight = false;
int multiParentRval = -1;
for(int i=0;i<edges.length;i++){
int lval = edges[i][0];
int rval = edges[i][1];
if(left.get(lval)==null){
Set<Integer> es = new HashSet<>();
es.add(i);
left.put(lval, es);
}else{
left.get(lval).add(i);
}
if(right.get(rval)==null){
Set<Integer> es = new HashSet<>();
es.add(i);
right.put(rval, es);
}else{
doubleRight = true;
multiParentRval = rval;
right.get(rval).add(i);
}
}
// if double right exist
Set<Integer> candidateIndexFromMultiParent = new HashSet<>();
if(doubleRight){
candidateIndexFromMultiParent = right.get(multiParentRval);
// return edges[candidateIndex.stream().max(Integer::compare).get()];
}
// if no double right, then it must be cycle, find the cycle
Set<Integer> candidateFromCycle = findCycleUntilAllDye(edges, left, right);
int finalIndex = -1;
if(!candidateIndexFromMultiParent.isEmpty() && !candidateFromCycle.isEmpty()){
//有环,又多父,取交集(理论上交集只有一个元素)
candidateIndexFromMultiParent.retainAll(candidateFromCycle);
finalIndex = candidateIndexFromMultiParent.stream().max(Integer::compare).get();
}
if(!candidateIndexFromMultiParent.isEmpty() && candidateFromCycle.isEmpty()){
//有多父,无环,取多父的最大者
finalIndex=candidateIndexFromMultiParent.stream().max(Integer::compare).get();
}
if(candidateIndexFromMultiParent.isEmpty() && !candidateFromCycle.isEmpty()){
//有环,无多父,取index最大者
finalIndex=candidateFromCycle.stream().max(Integer::compare).get();
}
return edges[finalIndex];
}
/**
* return index of cycle candidates
* @param edges
* @param left
* @param right
* @return
*/
Set<Integer> findCycleUntilAllDye(int[][] edges, Map<Integer, Set<Integer>> left, Map<Integer, Set<Integer>> right){
Map<Integer, Boolean> dyeMem = new HashMap<>();
for(int i=0;i<edges.length;i++){
if(Boolean.TRUE.equals(dyeMem.get(i))){
continue;
}
List<Integer> cycleIndex = cycle(edges, i, left, right, dyeMem);
if(cycleIndex.size()>1){
//发现环
return new HashSet<>(cycleIndex);
}
}
return new HashSet<>();
}
List<Integer> cycle(int[][] edges, int startIndex, Map<Integer, Set<Integer>> left, Map<Integer, Set<Integer>> right, Map<Integer, Boolean> dyeMem){
List<Integer> ret = new ArrayList<>();
ret.add(startIndex);
dyeMem.put(startIndex,true);
Integer nextEdge=startIndex;
int maxIndex = 0;
while((nextEdge=getNextEdge(edges, nextEdge, left, right))!=null){
dyeMem.put(nextEdge,true);
maxIndex = Math.max(maxIndex, nextEdge);
if(ret.contains(nextEdge)){ //!!!!!!!!!
return ret;
}
ret.add(nextEdge);
}
return Arrays.asList(-1);
}
private Integer getNextEdge(int[][] edges, int startEdgeIndex, Map<Integer, Set<Integer>> left, Map<Integer, Set<Integer>> right){
int rightVert = edges[startEdgeIndex][1];
Set<Integer> nextEdgesIndex = left.get(rightVert);
if(nextEdgesIndex==null || nextEdgesIndex.size()<1){
return null;
}else{
for(Iterator<Integer> it = nextEdgesIndex.iterator();it.hasNext();){
return it.next();
}
}
return null;
}
public static void main(String[] args) {
// int[][] edges = {{1,2}, {1,3}, {2,3}};
// int[][] edges = {{1,2},{2,3},{3,4},{4,1},{1,5}};
// int[][] edges = {{2,1},{3,1},{4,2},{1,4}};
// int[][] edges = {{1,2},{1,3},{2,3}};
int[][] edges = {{4,1},{1,5},{4,2},{5,1},{4,3}};
RedundantConnectionII demo = new RedundantConnectionII();
int[] ret = demo.findRedundantDirectedConnection(edges);
for(int i=0;i<ret.length;i++){
System.out.print(ret[i]+" ");
}
}
}
反思:写代码中出错的几个地方包括:
一开始没想到两种情况可能同时出现(既有双父又有环),也没有想到二者的交集可能还不止一个元素,这里的几何证明结论过于想当然。
另外不科学的一点是,上面的代码其实我仍然觉得有点问题,就是找环路的方法不对,可是不科学的是,OJ竟然过了,这尼玛怎么回事?我举个例子来说明我找环路的代码不对吧: [[1,2],[1,3],[2,4],[2,5],[3,6],[5,7],[5,8]], 这个图用我的代码跑会报错的,可是这图应该也是合乎题干要求的一个图啊
anyway, 一颗多叉树要找唯一环用回溯法做一下也不是什么难事,基于我对回溯法已经比较熟了,这里就不改了,读者有兴趣可以自己改完善些