LeetCode 0685.冗余连接 II:并查集(和I有何不同分析)——详细题解(附图)

【LetMeFly】685.冗余连接 II:并查集(和I有何不同分析)——详细题解(附图)

力扣题目链接:https://leetcode.cn/problems/redundant-connection-ii/

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1n)的树及一条附加的有向边构成。附加的边包含在 1n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 uivi 的一个父节点。

返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

 

示例 1:

输入:edges = [[1,2],[1,3],[2,3]]
输出:[2,3]

示例 2:

输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]

 

提示:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ui, vi <= n

解题方法:并查集

并查集

解题思路

这题和684.冗余连接的区别是:

684的无向图只需要考虑有没有形成自环,而本题有向图还需要考虑“是否形成了入度为2的节点”。

如果形成了“入度为2”的节点,例如下面两种情况,在684.冗余连接中只需要移除“首次形成(无向)环”的边,而在685.冗余连接II中就不能只移除“最后出现的导致形成(无向)环的边”:

1----->2      1------+
↑      ↑      ↑      ↓
3------+      2<-----3<---4

左图中只能移除[1,2][3,2]而不能移除[3,1];右图中只能移除[1,3]而不能移除[3,2][2,1]

有向边不能和无向边一概而论的本质原因是:树中一个节点不能有两个父节点,即入度不能为2。所以,一旦出现了入度为2的节点 n o d e node node,就要在“终点为 n o d e node node的两条边”里面选择一条移除。判断方法如下:

尝试移除一条边,判断剩下的边(不考虑方向)能否构成无向环,如果不构成无向环则说明这条边可以被移除。

判断方法就和684题一模一样了,使用并查集即可完成判断。

树上多一条边就一定存在入度为2的节点吗?不一定,还可能有以下这种情况:

1------+
↑      ↓
2<-----3----->4

图中节点[1,2,3]形成了一个环,但12344个节点的入度都为1

这样就和684题一模一样了其实,在环[1,2,3]里任意移除一条边图都能变成树。

同样使用并查集,返回第一条“形成环”的边即为所求。

解题方法

首先统计是否有入度为2的节点:

  • 若有,则尝试移除指向2的边(若移除后图中无环则这条边可以被移除)
  • 否则,移除第一条导致“环出现”的边

常见问题回答Q&A

Q1: 若有入度为2的节点,在判断“移除一条边后图是否为树”时,能否通过“统计每个点是否孤立(入度出度都为0)”来判断?

例如下图中终点为3的边有[1,3][4,3]两条,移除[4,3]的话会导致点4成为孤立点,因此只能移除[1,3]

1------+
↑      ↓
2<-----3<---4

A1: 不能这么判断。例如下图只能移除[2,4]不能移除[5,2],但其实移除其中的任意一条都不会产生“孤立点”。

+---+
↓   ↑
4-->2
    ↑
1-->5-->3

建议修改为“通过判断图是否联通”的方式判断某条边是否可以移除。

时空复杂度

  • 时间复杂度最坏 O ( n log ⁡ n ) O(n\log n) O(nlogn),平均为 O ( n α ( n ) ) O(n\alpha(n)) O(nα(n))(接近 O ( n ) O(n) O(n)
  • 空间复杂度 O ( n ) O(n) O(n)

AC代码

C++
class Solution {
private:
    vector<int> fa;

    bool couldRemoveThisEdge(vector<vector<int>>& edges, int index) {
        initFa(edges.size());
        for (int i = 0; i < edges.size(); i++) {
            if (i == index) {
                continue;
            }
            if (find(edges[i][0]) == find(edges[i][1])) {
                return false;
            }
            union_(edges[i][0], edges[i][1]);
        }
        return true;
    }

    vector<int> solution_indegree(vector<vector<int>>& edges, int node) {
        for (int i = edges.size() - 1; i >= 0; i--) {
            if (edges[i][1] == node && couldRemoveThisEdge(edges, i)) {
                return edges[i];
            }
        }
        return {};  // FAKE RETURN
    }

    int find(int x) {
        if (x != fa[x]) {
            fa[x] = find(fa[x]);
        }
        return fa[x];
    }

    void union_(int x, int y) {
        fa[find(x)] = find(y);
    }

    void initFa(int n) {
        fa.resize(n + 1);
        for (int i = 1; i <= n; i++) {
            fa[i] = i;
        }
    }

    vector<int> solution_unionFind(vector<vector<int>>& edges) {
        initFa(edges.size());
        for (vector<int>& edge : edges) {
            if (find(edge[0]) == find(edge[1])) {
                return edge;
            } else {
                union_(edge[0], edge[1]);
            }
        }
        return {};  // FAKE RETURN
    }
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        vector<bool> inDegree(edges.size() + 1);
        for (vector<int>& edge : edges) {
            if (inDegree[edge[1]]) {  // 找到了入度为2的点
                return solution_indegree(edges, edge[1]);
            } else {
                inDegree[edge[1]] = true;
            }
        }
        return solution_unionFind(edges);
    }
};
Python
from typing import List

class Solution:
    def initFa(self) -> None:
        for i in range(1, len(self.edges) + 1):
            self.fa[i] = i
    
    def find(self, x: int) -> int:
        if self.fa[x] != x:
            self.fa[x] = self.find(self.fa[x])
        return self.fa[x]
    
    def union(self, x: int, y: int) -> None:
        self.fa[self.find(x)] = self.find(y)
    
    def couldRemoveThisEdge(self, index: int) -> bool:
        self.initFa()
        for i in range(len(self.edges)):
            if i == index:
                continue
            if self.find(self.edges[i][0]) == self.find(self.edges[i][1]):
                return False
            else:
                self.union(self.edges[i][0], self.edges[i][1])
        return True

    def solution_indegree(self, node: int) -> List[int]:
        for i in range(len(self.edges) - 1, -1, -1):
            if self.edges[i][1] == node and self.couldRemoveThisEdge(i):
                return self.edges[i]
        return []  # FAKE RETURN

    def solution_unionFind(self) -> List[int]:
        self.initFa()
        for x, y in self.edges:
            if self.find(x) == self.find(y):
                return [x, y]
            else:
                self.union(x, y)
        return []  # FAKE RETURN
    
    def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
        self.fa = [0] * (len(edges) + 1)
        self.edges = edges
        hasIndegree = [False] * (len(edges) + 1)
        for x, y in edges:
            if hasIndegree[y]:
                return self.solution_indegree(y)
            else:
                hasIndegree[y] = True
        return self.solution_unionFind()
Java
class UnionFind {
    private int[] fa;

    public UnionFind(int n) {
        fa = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            fa[i] = i;
        }
    }

    private int find(int x) {
        if (fa[x] != x) {
            fa[x] = find(fa[x]);
        }
        return fa[x];
    }

    public boolean isUnion(int x, int y) {
        return find(x) == find(y);
    }

    public void union(int x, int y) {
        fa[find(x)] = find(y);
    }
}

class Solution {
    private boolean canRemoveThisEdge(int[][] edges, int index) {
        UnionFind unionFind = new UnionFind(edges.length);
        for (int i = 0; i < edges.length; i++) {
            if (i == index) {
                continue;
            }
            if (unionFind.isUnion(edges[i][0], edges[i][1])) {
                return false;
            } else {
                unionFind.union(edges[i][0], edges[i][1]);
            }
        }
        return true;
    }

    private int[] solution_indegree(int[][] edges, int node) {
        for (int i = edges.length - 1; i >= 0; i--) {
            if (edges[i][1] == node && canRemoveThisEdge(edges, i)) {
                return edges[i];
            }
        }
        return new int[0];  // FAKE RETURN
    }

    private int[] solution_unionFind(int[][] edges) {
        UnionFind unionFind = new UnionFind(edges.length);
        for (int[] edge : edges) {
            if (unionFind.isUnion(edge[0], edge[1])) {
                return edge;
            } else {
                unionFind.union(edge[0], edge[1]);
            }
        }
        return new int[0];  // FAKE RETURN
    }
    
    public int[] findRedundantDirectedConnection(int[][] edges) {
        boolean[] hasIndegree = new boolean[edges.length + 1];
        for (int[] edge : edges) {
            if (hasIndegree[edge[1]]) {
                return solution_indegree(edges, edge[1]);
            } else {
                hasIndegree[edge[1]] = true;
            }
        }
        return solution_unionFind(edges);
    }
}
Go
package main

type UnionFind struct {
    fa []int
}

func New(n int) UnionFind {
    fa := make([]int, n + 1)
    for th, _ := range fa {
        fa[th] = th
    }
    return UnionFind{fa}
}

func (unionFind UnionFind) _find(x int) int {
    if unionFind.fa[x] != x {
        unionFind.fa[x] = unionFind._find(unionFind.fa[x])
    }
    return unionFind.fa[x]
}

func (unionFind UnionFind) isUnion(x, y int) bool {
    return unionFind._find(x) == unionFind._find(y)
}

func (unionFind UnionFind) union(x, y int) {
    unionFind.fa[unionFind._find(x)] = unionFind._find(y)
}

func canRemoveThisEdge(edges [][]int, index int) bool {
    unionFind := New(len(edges))
    for i := 0; i < len(edges); i++ {
        if i == index {
            continue
        }
        if unionFind.isUnion(edges[i][0], edges[i][1]) {
            return false
        } else {
            unionFind.union(edges[i][0], edges[i][1])
        }
    }
    return true
}

func solution_indegree(edges [][]int, node int) []int {
    for i := len(edges) - 1; i >= 0; i-- {
        if edges[i][1] == node && canRemoveThisEdge(edges, i) {
            return edges[i]
        }
    }
    return make([]int, 0)  // FAKE RETURN
}

func solution_unionFind(edges [][]int) []int {
    unionFind := New(len(edges))
    for _, edge := range edges {
        if unionFind.isUnion(edge[0], edge[1]) {
            return edge
        } else {
            unionFind.union(edge[0], edge[1])
        }
    }
    return make([]int, 0)  // FAKE RETURN
}

func findRedundantDirectedConnection(edges [][]int) []int {
    hasIndegree := make([]bool, len(edges) + 1)
    for _, edge := range edges {
        if hasIndegree[edge[1]] {
            return solution_indegree(edges, edge[1])
        } else {
            hasIndegree[edge[1]] = true
        }
    }
    return solution_unionFind(edges)
}

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

Tisfy:https://letmefly.blog.csdn.net/article/details/143470538

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/911605.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git原理与上传

言&#xff1a; git是一个软件&#xff0c;gitee/github是一个网站&#xff0c;这里有什么联系吗&#xff1f;我们身为一个程序员不可能不知道github&#xff0c;但是毕竟这是外国的网站&#xff0c;我们不翻墙的情况下&#xff0c;是无法访问的(或者就是太慢了&#xff0c;或…

Go语言的常用内置函数

文章目录 一、Strings包字符串处理包定义Strings包的基本用法Strconv包中常用函数 二、Time包三、Math包math包概述使用math包 四、随机数包&#xff08;rand&#xff09; 一、Strings包 字符串处理包定义 Strings包简介&#xff1a; 一般编程语言包含的字符串处理库功能区别…

React 入门课程 - 使用CDN编程React

1. 第一个React 注意&#xff1a;在vscode里&#xff0c;使用Live Server来运行html文件。 index.html <html><head><link rel"stylesheet" href"index.css"><script crossorigin src"https://unpkg.com/react17/umd/react.de…

苍穹外卖day09超出配送范围前端不提示问题

同学们在写苍穹外卖项目day09时调用了百度地图api来判断用户地址是否超出配送范围&#xff0c; 但是在黑马官方的课程或资料中&#xff0c;出现这样的问题时只会向用户端的控制台报错并不会提醒用户 如下图&#xff1a; 解决方法&#xff1a; 其实解决方法很简单只需要找到向…

ARXML汽车可扩展标记性语言规范讲解

ARXML: Automotive Extensible Markup Language &#xff08;汽车可扩展标记语言&#xff09; xmlns: Xml name space &#xff08;xml 命名空间&#xff09; xsd: Xml Schema Definition (xml 架构定义) 1、XML与HTML的区别&#xff0c;可扩展。 可扩展&#xff0c;主要是…

【开源项目】经典开源项目数字孪生智慧小镇——开源工程及源码

飞渡科技数字孪生小镇管理平台&#xff0c;依托自研数字孪生引擎平台&#xff0c;将5G、物联网、大数据、人工智能等数字化技术融合应用&#xff0c;采集、整合、应用小镇的规划、运营、管理等数据&#xff0c;实现特色小镇全域管理系统化以及精细化。 基于地理信息系统&#x…

探索 Move 编程语言:智能合约开发的新纪元

目录 引言 一、变量的定义 二、整型 如何在Move中表示小数和负数&#xff1f; 三、运算符 as运算符 布尔型 地址类型 四、什么是包&#xff1f; 五、什么是模块&#xff1f; 六、如何定义方法&#xff1f; 方法访问权限控制 init方法 总结 引言 Move 是一种专为区…

智能提醒助理系列-jdk8升级到21,springboot2.3升级到3.3

本系列文章记录“智能提醒助理”产品建设历程&#xff0c;记录实践经验、巩固知识点、锻炼总结能力。 本篇介绍技术栈升级的过程&#xff0c;遇到的问题和解决方案。 一、需求出发点 智能提醒小程序 当前使用的是jdk8&#xff0c;springboot2.3,升级到jdk21和springboot3.3 学…

算法|牛客网华为机试31-40C++

牛客网华为机试 上篇&#xff1a;算法|牛客网华为机试21-30C 文章目录 HJ31 单词倒排HJ32 密码截取HJ33 整数与IP地址间的转换HJ34 图片整理HJ35 蛇形矩阵HJ36 字符串加密HJ37 统计每个月兔子的总数HJ38 求小球落地5次后所经历的路程和第5次反弹的高度HJ39 判断两个IP是否属于同…

第六十三周周报 GCN-CNNGA

文章目录 week 63 GCN-CNNGA摘要Abstract1. 题目2. Abstract3. 文献解读3.1 Introduction3.2 创新点 4. 网络结构4.1 数据分析4.2 混合深度学习框架的发展4.3 Mul4.4 CNN block4.5 GCN block4.6 GRU block4.7 注意力机制4.8 模型评估标准 5. 实验结果5.1 不同邻接矩阵的性能评价…

人工智能——小白学习指南

知孤云出岫 目录 1. **智能评测系统**2. **个性化学习路径推荐**3. **虚拟学习助手**4. **学习行为分析**5. **数据驱动的教学决策**6. **自动化课程推荐**7. **数据隐私与安全保护** 人工智能知识点的总结和学习路线&#xff0c;以数据表格形式呈现&#xff0c;并附带在教育行…

「Mac畅玩鸿蒙与硬件21」鸿蒙UI组件篇11 - Canvas 组件的静态进阶应用

在鸿蒙应用开发中&#xff0c;Canvas 组件不仅用于基础绘图&#xff0c;还提供了处理复杂路径和渐变效果的多种手段&#xff0c;帮助开发者实现精美的静态图形。本篇将介绍如何在 Canvas 中绘制复杂路径、创建渐变填充效果。 关键词 Canvas 组件复杂路径绘制渐变填充 一、Canv…

【自动化测试】APP UI 自动化(安卓)-本地环境搭建

一、软件准备及版本介绍 软件版本JAVA-SDK1.8.0_181 python 3.10.10 Android SDK Tools 下最新版本即可&#xff0c;无特殊要求 PyCharm 2023.3.5&#xff08;下最新版本即可&#xff0c;无特殊要求&#xff09; 二、安装步骤及环境变量配置 2.1 Java安装及配置 1&am…

【动手学电机驱动】 STM32-FOC(2)STM32 导入和创建项目

STM32-FOC&#xff08;1&#xff09;STM32 电机控制的软件开发环境 STM32-FOC&#xff08;2&#xff09;STM32 导入和创建项目 STM32-FOC&#xff08;3&#xff09;STM32 三路互补 PWM 输出 STM32-FOC&#xff08;4&#xff09;IHM03 电机控制套件介绍 STM32-FOC&#xff08;5&…

鸿蒙进阶篇-网格布局 Grid/GridItem(二)

hello大家好&#xff0c;这里是鸿蒙开天组&#xff0c;今天让我们来继续学习鸿蒙进阶篇-网格布局 Grid/GridItem&#xff0c;上一篇博文我们已经学习了固定行列、合并行列和设置滚动&#xff0c;这一篇我们将继续学习Grid的用法&#xff0c;实现翻页滚动、自定义滚动条样式&…

【笔记】变压器-热损耗-频响曲线推导 - 04 额定功率处损耗特性

0.最大的问题 - 散热 对变压器这类功率器件&#xff0c;最大的问题是散热的效率。因为传统的电路基板热导率并不高&#xff0c;几乎和良性导热材料有近乎两个数量级的导热差异&#xff0c;所以&#xff0c;会采用特殊的导热技术&#xff0c;把热量尽可能快地传导到散热片。 传…

MATLAB中eig函数用法

目录 语法 说明 示例 矩阵特征值 矩阵的特征值和特征向量 排序的特征值和特征向量 左特征向量 不可对角化&#xff08;亏损&#xff09;矩阵的特征值 广义特征值 病态矩阵使用 QZ 算法得出广义特征值 一个矩阵为奇异矩阵的广义特征值 eig函数的功能是求取矩阵特征值…

深入理解单位根:如何通过单位根检验分析序列的平稳性

在时间序列分析中&#xff0c;平稳性是至关重要的概念。大多数时间序列模型&#xff08;如 ARMA 模型&#xff09;都假设序列是平稳的&#xff0c;即其统计特性&#xff08;均值、方差、自相关性&#xff09;不随时间变化。然而&#xff0c;许多实际数据并不满足这一条件&#…

书生大模型第三关Git 基础知识

关卡编号&#xff1a;L0G3000 任务一 破冰行动 fork仓库&#xff0c;注意这里不要勾选Copy branch Only!!!&#xff0c;因为后面课程中会使用到class分支&#xff1a; 克隆仓库&#xff1a; 移动分支&#xff1a; 创建自己的分支&#xff1a; 创建id.md文档&#xff0c;…

由中文乱码引来的一系列学习——Qt

前言 解决中文引起的乱码&#xff0c;并不难&#xff0c;网上一搜就有好几个方法任君选择&#xff0c;但是解决乱码的这些方法的原理是什么&#xff0c;我一直没太明白。 这次项目需要在Android环境下运行&#xff0c;而根据Qt跨平台的特性&#xff0c;我一般是在Windows环境…