【并查集】685. 冗余连接II

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。
返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

解题思路:分类讨论 + 并查集

这道题乍一看,和684. 冗余连接非常像!只不过从无向变成了有向。
但是这个难度提升了一个喜马拉雅山,主要是要想好一共有几种情况,针对每种情况去解决。

1、分析

这道题分类讨论非常关键!
这道题分类讨论非常关键!
这道题分类讨论非常关键!
(重要的事情说三遍,不然你会马上晕的就不知道天南地北了)

首先我们要明确一个观念,题目说了,人家本来就是一颗有向树,只不过现在在这个基础上新增了一条边,变成了错误的一棵树。而我们只要删除一条错误的边,就可以纠正了!【也就是说,即使有多种删除方案,但只要删除一条,而且是边集中最右边的那条】
——那么如何找到这条错误的边呢?

● 我们先从有向树的定义入手:
(1) 每个孩子仅有一个父节点;
(2) 有且仅有一个根节点。

      
⭐ 对应的,这条错误的边(我们认为只有一条)一定导致以下异常情况之一
● 情况一:有一个节点有两个父节点;
——问题:为什么不是3个,4个或者更多呢?
 因为如果是3个,我们就要删除2条边,4个就要删除3条边了!这不符合题目要求。
——问题:那为什么不是0个呢?
 一棵标准的有向树会有一个根节点,它的父节点是0不是很正常吗!!
● 情况二:(每个节点仅有一个父节点)没有根节点【形成有向环】。
——问题:会不会有多个根节点呢?
 不会!因为如果存在多个根节点,就算不删除边的情况下,树都无法连通。(更何况我们还要删除一条边)

2、解题思路:

● 处理情况一:假设有节点node,他的两个父节点分别是parent1、parent2。那么必然删除边:[parent1, node]或者[parent2, node]。
 我们可以尝试去删除第二条出现在边集的边,假设是[parent2, node]
 (1) 如果删除后,剩余边集可以构成一棵有向树。那么我们直接删除[parent2, node]即可
 因为就算删除第一条边也可以构成有向树,但是因为我们要返回的是靠右的边。
 (2) 如果删除后,剩余边集不可以构成一棵有向树。那么我们只能直接删除[parent1, node]

⭕ 那——怎么判断是否可以构成有向树?
利用并查集。定义connect[i]为节点 i 的父节点所在集合,初始每个节点的connect[i]是自己。
① 连通。如果并查集中所有节点的父节点集合是同一个,说明是连通的;
② 没有环。是的!你没有看错,【异常情况一有可能会出现环!】
如下图所示,如果此时删除[3, 2],那么会剩下环。

异常情况一:既有两个父节点,也有环

判断是否有环:就很简单了,根据并查集,【如果父节点的connect集合和子节点的connect集合一样,就说明如果加入这条边会导致有向环!】,此时不能构成有向树。

以上两个条件均满足,就说明可以构成树!如果有一个不满足就不能构成树。那么要先判断哪个呢?

如果我们直接先进行步骤①,会导致什么结果?
此时节点1、2、4的父节点集合是什么?——无限循环,根本找不到它的父节点集合!\color{red}{所以这里要先处理有环的情况,再处理连通的情况。}

● 处理情况二:这里就很简单了,和情况一的第②步处理一样,只要出现环,那么这条边一定要删除。

⭐ 另外,先判断情况一,在情况一不满足的情况下,再去处理情况二!
——为什么?你也看到了,情况二是通过环来处理的,但是情况一也可能会有环。
那我如果现在有环,是哪个情况呢?
如果是情况一,是删除两个父节点连接边其中之一呢?还是删除环那条呢?情况就太复杂了。
如果先处理情况一,判断确实没有任何一个点的父节点超过两个。那么我们就能肯定是情况二,返回出现环的边。

3、代码实现
class Solution {
    private int[] connect;
    public int[] findRedundantDirectedConnection(int[][] edges) {
        // 分析题目:已经是一个有向树的情况下,新增一条边,导致有向树不满足定义。
        //          因此,只要删除一条有问题的边,剩下的一定是一颗有向树。
        
        // 看两种不符合条件的情况
        // 1. 某一个节点存在两个父节点
        // 2. 不是情况1时,说明每个节点仅有一个父节点,此时一定存在有向环(没有根)

        // 初始化并查集
        connect = new int[edges.length + 1];
        for(int i=0; i<connect.length; i++){
            connect[i] = i;
        }
        
        int[] indegree = new int[edges.length+1];
        int i = 0;
        for(; i<edges.length; i++){
            indegree[edges[i][1]]++;
            if(indegree[edges[i][1]] > 1) break;
        }

        // 1. 第一种情况:某个节点有两个父节点(为什么现要判断第一种情况呢,因为第一种情况也会出现环这种情况,没办法解耦)
        if(i < edges.length){
            // 一定是删除其中一个父节点连接到该节点的边

            // 如果删除第二条出现的边
            // (1) 能构成树,说明应该删除第二条出现的边;
            // (2) 不能构成树,说明应该删除第一条边
            int j = 0;
            for(; j<edges.length; j++){
                if(edges[j][1] == edges[i][1]) break;
            }
            if(isTree(edges, i)) return new int[]{edges[i][0], edges[i][1]};
            else return new int[]{edges[j][0], edges[j][1]};
        }else{
            // 说明此时没有根,存在有向环
            i = 0;
            for(; i<edges.length; i++){
                if(!merge(edges[i][0], edges[i][1])) break;
            }
            return new int[]{edges[i][0], edges[i][1]};
        }

    }
    public int find(int x){
        while(x != connect[x]){
            x = connect[x];
        }
        return x;
    }
    // 父节点是x, 孩子节点是y,有x→y
    public boolean merge(int x, int y){
        int x_root = find(x);
        int y_root = find(y);
        if(x_root == y_root) return false;
        else{
            connect[y_root] = x_root; // 有向,必须是这个合并顺序
            return true;
        }
    }
    // 如果删除这条边后,并查集满足:是全连通 + 没有环,就说明是树
    public boolean isTree(int[][] edges, int removeIdx){
        // 环检查
        for(int i=0; i<edges.length; i++){
            if(i == removeIdx) continue;
            if(!merge(edges[i][0], edges[i][1])) return false;
        }
        // 是全连通图
        int root = -1;
        for(int i=1; i<connect.length; i++){
            int tmp = find(i);
            if(root == -1) root = tmp;
            else if(tmp != root) return false;
        }
        return true;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容