【算法与数据结构】684、685、LeetCode冗余连接I II

news2024/9/28 6:29:53

文章目录

  • 一、684、冗余连接 I
  • 二、685、冗余连接 II
  • 三、完整代码

所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。

一、684、冗余连接 I

在这里插入图片描述
在这里插入图片描述

  思路分析:题目给出一个无向有环图,要求去掉一个边以后构成一个树(多叉树)。那么我们根据并查集理论,将所有的边加入到并查集中,前面的边先连上,边上的两个节点如果不在同一个集合中,就加入集合。如果两个节点已经出现在同一集合里,说明这两个节点已经连接在一起了,再加入一条后来的边就会构成环。因此去掉后来的这条边即可。

  程序如下

class Solution {
private:
    int n = 200005;		// 节点数量 200000
    vector<int> father = vector<int>(n, 0);	// C++里面的一种数据结构
    // 并查集初始化
    void init() {
        for (int i = 0; i < n; ++i) {
            father[i] = i;
        }
    }
    // 并查集里寻根的过程
    int find(int u) {
        return u == father[u] ? u : father[u] = find(father[u]);    // 路径压缩
    }

    // 判断 u 和 v是否找到同一个根
    bool isSame(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }

    // 将v->u 这条边加入并查集
    void join(int u, int v) {
        u = find(u); // 寻找u的根
        v = find(v); // 寻找v的根
        if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
        father[v] = u;      // 根不同,则令v的父节点为u
    }
public:
	vector<int> findRedundantConnection(vector<vector<int>>& edges) {
		init();
		for (int i = 0; i < edges.size(); i++) {
			if (isSame(edges[i][0], edges[i][1])) return edges[i];
			else join(edges[i][0], edges[i][1]);
		}
		return { };
	}
};

复杂度分析:

  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),其中 n n n是图中边的个数,即edges数组的大小。需要遍历图中的 n n n条边,对于每条边,需要对两个节点查找祖先,如果两个节点的祖先不同则需要进行合并,需要进行2次查找和最多1次合并。一共需要进行 2 n 2n 2n次查找和最多 n n n次合并,因此总时间复杂度是 O ( 2 n log ⁡ ⁡ n ) = O ( n log ⁡ n ) O(2n \log ⁡n)=O(n \log n) O(2nlogn)=O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n),主要开销用于father数组。

二、685、冗余连接 II

在这里插入图片描述
在这里插入图片描述

  思路分析:题目说明,图原本是一棵树,只不过在不增加节点的情况下多了一条额外的边,我们需要把多出来的这一条边去除。与684题区别在于本题是有向图,684题是无向图。关于有向图有出度和入度的说法。出度是指节点发出的箭头数量,入度是指指向节点的箭头数量。根节点没有父节点,其他节点有且只有一个父节点,那么多出来的一条边就会改变了节点的入度数量,而出度的数量无法成为判断标准(一个父节点可以由多个子节点,出度数量不唯一)。出现入度为2的节点有以下两种情况:

在这里插入图片描述

  如果加入的这条边形成了有向环,那么入度不会改变:
在这里插入图片描述
  统计节点入度:

int inDegree[N] = {0}; // 记录节点入度
n = edges.size(); // 边的数量
for (int i = 0; i < n; i++) {
    inDegree[edges[i][1]]++; // 统计入度
}

  前两种入度为2的情况一定是删除入度为2的节点的两条边其中一条。题目还要求返回最后出现在二维数组的答案,也就是说要从后往前遍历,删除以后判断剩下的图是否构成树。如果说两条边都可以构成树,就删除最后一条边。

vector<int> vec; // 记录入度为2的边(如果有的话就两条边)
// 找入度为2的节点所对应的边,注意要倒序,因为优先返回最后出现在二维数组中的答案
for (int i = n - 1; i >= 0; i--) {
    if (inDegree[edges[i][1]] == 2) {
        vec.push_back(i);
    }
}
// 处理图中情况1 和 情况2
// 如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树
if (vec.size() > 0) {
    if (isTreeAfterRemoveEdge(edges, vec[0])) {
        return edges[vec[0]];
    } else {
        return edges[vec[1]];
    }
}

  情况三,明确没有入度为2的情况,一定是有环,我们从后往前遍历,找到删除以后的那个可以构成树的边。那么如何判断一个图是否为树,应该应用到并查集了。因为如果两个点所在的边在添加图之前如果就可以在并查集里找到了相同的根,那么这条边添加上之后 这个图一定不是树了。

// 情况三:在有向图里找到删除的那条边,使其变成树
    vector<int> getRemoveEdge(const vector<vector<int>>& edges) {
        init(); // 初始化并查集
        for (int i = 0; i < n; i++) { // 遍历所有的边
            if (isSame(edges[i][0], edges[i][1])) { // 构成有向环了,就是要删除的边
                return edges[i];
            }
            join(edges[i][0], edges[i][1]);
        }
        return {};
    }

  程序如下

// 685、冗余连接II-并查集
class Solution2 {
private:
    static const int N = 1005;		// 节点数量 1005
    int father[N];
    int n;                          // 边的数量
    
    // 并查集初始化
    void init() {
        for (int i = 0; i < n; i++) {
            father[i] = i;
        }
    }
    // 并查集里寻根的过程
    int find(int u) {
        return u == father[u] ? u : father[u] = find(father[u]);    // 路径压缩
    }

    // 判断 u 和 v是否找到同一个根
    bool isSame(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }

    // 将v->u 这条边加入并查集
    void join(int u, int v) {
        u = find(u); // 寻找u的根
        v = find(v); // 寻找v的根
        if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
        father[v] = u;      // 根不同,则令v的父节点为u
    }

    // 情况三:在有向图里找到删除的那条边,使其变成树
    vector<int> getRemoveEdge(const vector<vector<int>>& edges) {
        init(); // 初始化并查集
        for (int i = 0; i < n; i++) { // 遍历所有的边
            if (isSame(edges[i][0], edges[i][1])) { // 构成有向环了,就是要删除的边
                return edges[i];
            }
            join(edges[i][0], edges[i][1]);
        }
        return {};
    }

    // 删一条边之后判断是不是树
    bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {
        init(); // 初始化并查集
        for (int i = 0; i < n; i++) {
            if (i == deleteEdge) continue;
            if (isSame(edges[i][0], edges[i][1])) { // 构成有向环了,一定不是树
                return false;
            }
            join(edges[i][0], edges[i][1]);
        }
        return true;
    }
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        int inDegree[N] = { 0 }; // 记录节点入度
        n = edges.size(); // 边的数量
        for (int i = 0; i < n; i++) {
            inDegree[edges[i][1]]++; // 统计入度
        }
        vector<int> vec; // 记录入度为2的边(如果有的话就两条边)
        // 找入度为2的节点所对应的边,注意要倒序,因为优先返回最后出现在二维数组中的答案
        for (int i = n - 1; i >= 0; i--) {
            if (inDegree[edges[i][1]] == 2) {
                vec.push_back(i);
            }
        }

        // 情况一和情况二:如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树
        if (vec.size() > 0) {
            if (isTreeAfterRemoveEdge(edges, vec[0])) {
                return edges[vec[0]];
            }
            else {
                return edges[vec[1]];
            }
        }

        // 情况三:明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了
        return getRemoveEdge(edges);
    }
};

复杂度分析:

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn)
  • 空间复杂度: O ( n ) O(n) O(n)

三、完整代码

# include <iostream>
# include <vector>
using namespace std;

// 684、冗余连接I-并查集
class Solution {
private:
    int n = 200005;		// 节点数量 200000
    vector<int> father = vector<int>(n, 0);	// C++里面的一种数据结构
    // 并查集初始化
    void init() {
        for (int i = 0; i < n; i++) {
            father[i] = i;
        }
    }
    // 并查集里寻根的过程
    int find(int u) {
        return u == father[u] ? u : father[u] = find(father[u]);    // 路径压缩
    }

    // 判断 u 和 v是否找到同一个根
    bool isSame(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }

    // 将v->u 这条边加入并查集
    void join(int u, int v) {
        u = find(u); // 寻找u的根
        v = find(v); // 寻找v的根
        if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
        father[v] = u;      // 根不同,则令v的父节点为u
    }
public:
	vector<int> findRedundantConnection(vector<vector<int>>& edges) {
		init();
		for (int i = 0; i < edges.size(); i++) {
			if (isSame(edges[i][0], edges[i][1])) return edges[i];
			else join(edges[i][0], edges[i][1]);
		}
		return { };
	}
};

// 685、冗余连接II-并查集
class Solution2 {
private:
    static const int N = 1005;		// 节点数量 1005
    int father[N];
    int n;                          // 边的数量
    
    // 并查集初始化
    void init() {
        for (int i = 0; i < n; i++) {
            father[i] = i;
        }
    }
    // 并查集里寻根的过程
    int find(int u) {
        return u == father[u] ? u : father[u] = find(father[u]);    // 路径压缩
    }

    // 判断 u 和 v是否找到同一个根
    bool isSame(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }

    // 将v->u 这条边加入并查集
    void join(int u, int v) {
        u = find(u); // 寻找u的根
        v = find(v); // 寻找v的根
        if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
        father[v] = u;      // 根不同,则令v的父节点为u
    }

    // 情况三:在有向图里找到删除的那条边,使其变成树
    vector<int> getRemoveEdge(const vector<vector<int>>& edges) {
        init(); // 初始化并查集
        for (int i = 0; i < n; i++) { // 遍历所有的边
            if (isSame(edges[i][0], edges[i][1])) { // 构成有向环了,就是要删除的边
                return edges[i];
            }
            join(edges[i][0], edges[i][1]);
        }
        return {};
    }

    // 删一条边之后判断是不是树
    bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {
        init(); // 初始化并查集
        for (int i = 0; i < n; i++) {
            if (i == deleteEdge) continue;
            if (isSame(edges[i][0], edges[i][1])) { // 构成有向环了,一定不是树
                return false;
            }
            join(edges[i][0], edges[i][1]);
        }
        return true;
    }
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        int inDegree[N] = { 0 }; // 记录节点入度
        n = edges.size(); // 边的数量
        for (int i = 0; i < n; i++) {
            inDegree[edges[i][1]]++; // 统计入度
        }
        vector<int> vec; // 记录入度为2的边(如果有的话就两条边)
        // 找入度为2的节点所对应的边,注意要倒序,因为优先返回最后出现在二维数组中的答案
        for (int i = n - 1; i >= 0; i--) {
            if (inDegree[edges[i][1]] == 2) {
                vec.push_back(i);
            }
        }

        // 情况一和情况二:如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树
        if (vec.size() > 0) {
            if (isTreeAfterRemoveEdge(edges, vec[0])) {
                return edges[vec[0]];
            }
            else {
                return edges[vec[1]];
            }
        }

        // 情况三:明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了
        return getRemoveEdge(edges);
    }
};

int main() {
 //   // 684、冗余连接I-并查集-测试案例
	//vector<vector<int>> edges = { {1, 2}, {1, 3}, {2, 3} };
	//Solution s1;
	//vector<int> result = s1.findRedundantConnection(edges);

    // 685、冗余连接II-并查集-测试案例
    vector<vector<int>> edges = { {1, 2}, {1, 3}, {2, 3} };
    Solution2 s2;
    vector<int> result = s2.findRedundantDirectedConnection(edges);

	for (vector<int>::iterator it = result.begin(); it < result.end(); it++) {
		cout << *it << ' ';
	}
	cout << endl;
	system("pause");
	return 0;
}

end

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyQt6的开发流程(密码生成小程序为例)

PyQt6的开发流程&#xff08;密码生成小程序为例&#xff09; 文章目录 PyQt6的开发流程&#xff08;密码生成小程序为例&#xff09;一、流程介绍与概览1. 界面与逻辑分离的开发流程2. PyQt6的开发流程 二、打开 designer.exe 创建文件三、用QT设计师绘制界面保存成ui1. QT常用…

急中生智:献血200cc没事,为啥出血200cc就可能噶?

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 声湃轩北京录音间 外伤出血更常见&#xff0c;但同样可能危及生命。 众所周知&#xff0c;出血是一种常见的外伤和急症&…

家装服务管理:Java技术的创新应用

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

ONLYOFFICE 桌面编辑器 v8.0 更新内容详细攻略

文章目录 引言PDF 表单RTL 支持电子表格中的新增功能Moodle 集成用密码保护 PDF 文件从“开始”菜单快速创建文档本地界面主题下载安装桌面编辑工具总结 引言 官网链接&#xff1a; ONLYOFFICE 官方网址 ONLYOFFICE 桌面编辑器是一款免费的文档处理软件&#xff0c;适用于 Li…

APP被针对攻击了,要怎么解决

随着APP行业的兴起&#xff0c;游戏公司异军突起&#xff0c;不管是在控证还是攻击方面都是属于最复杂的一个场面&#xff0c;游戏APP逐渐成为DDOS流量攻击的“重灾区”。没有提前做好了解就盲目进军游戏APP行业&#xff0c;一旦被攻击就会让公司束手无策。那么&#xff0c;刚上…

基于springboot实现的海鲜销售系统

一、系统架构 前端&#xff1a;html | bootstrap | vue | js | css 后端&#xff1a;springboot | springdata-jpa 环境&#xff1a;jdk1.8 | mysql | maven | redis 二、代码及数据库 三、功能介绍 01. web端-注册 02. web端-登录 03. web端-首页 04. web端-…

k8s节点负载使用情况分析命令kubectl describe node [node-name]

1.到任意安装了kubectl节点命令的节点上执行kubectl describe node [node-name] 上面的Requests最小分配 Limits最大分配是所有pod之和&#xff0c;最小分配之和不能超过服务器实际参数&#xff0c;否则新的pod会因为资源不够起不来&#xff0c;最大分配是预设之和&#xff0…

移动端学习:如何把exe转换成apk

exe转换成apk是怎么实现的呢?-电脑端-一门科技将exe文件转换成apk文件是一个比较常见的需求,尤其是对于一些开发者和用户来说。但是,这个过程并不是简单的复制和粘贴。在本文中,我们将介绍exe转换成apk的原理和详细介绍。首先,我们需要了解什么https://www.yimenapp.net/k…

数据安全-动态加密(不同敏感字段使用不同的加密算法-MySQL、Oracle版本)

动态数据加密 动态加密&#xff08;也称实时加密&#xff0c;透明加密等&#xff0c;其英文名为encrypt on-the-fly&#xff09;&#xff0c;是指数据在使用过程中自动对数据进行加密或解密操作&#xff0c;无需用户的干预&#xff0c;合法用户在使用加密的文件前&#xff0c;…

服务器权限:Error: EACCES: permission denied, open‘/Cardiac/uniquC.csv

背景&#xff1a; 我想在服务器上传一个文件uniquC.csv&#xff0c;但是服务器说我没有权限 解决方案&#xff1a; 1. 查看目前是否存在对文件夹的权限 ls -ld /Cardiac/ # your fold path 此时&#xff0c;我发现 这也意味着root也没有赋予写的权限。 2. 拿到root权限 …

Python爬虫-模拟Github登录并获取个人信息

爬虫系列&#xff1a;http://t.csdnimg.cn/WfCSx 前言 很多情况下&#xff0c;页面的某些信息需要登录才可以查看。对于爬虫来说&#xff0c;需要爬取的信息如果需要登录才可以看到的话&#xff0c;那么我们就需要做一些模拟登录的事情。 在前面我们了解了会话和 Cookies 的…

人脸2D和3D道具SDK解决方案提供商

人脸识别和增强现实技术成为了许多企业和开发者关注的焦点&#xff0c;为了满足市场对高质量、易于集成的人脸识别SDK的需求&#xff0c;美摄科技推出了一系列领先的人脸2D/3D道具SDK解决方案。 一、产品特点 高精度识别&#xff1a;美摄科技的人脸识别技术采用深度学习算法&…

【博士每天一篇文献-综述】A Modified Echo State Network Model Using Non-Random Topology

阅读时间&#xff1a;2023-11-23 1 介绍 年份&#xff1a;2023 作者&#xff1a; Arroyo, Diana Carolina Roca&#xff0c;数学与计算机科学研究所&#xff08;ICMC&#xff09;圣保罗大学 (USP) 期刊&#xff1a; 博士论文 引用量&#xff1a;0 这篇论文是一篇博士论文&am…

PostgreSQL 与MySQL 对比使用

一、前言 博主的系统既有 用到MySQL 也有用到PostgreSQL &#xff0c;之所以用到这两种数据库&#xff0c;主要是现在都是国产替代&#xff0c;虽然说这两款数据库也不是国产的&#xff0c;但是相对开源&#xff0c;oracle是不让用了。所以现在使用比较多的就是这两个关系型数据…

geotools解析shp 提示 opengis.*.SimpleFeatureType‘ 不在其界限内

问题:&#xff08; geotools.version&#xff1a;31-SNAPSHOT&#xff09; 解析shp文件时提示类型SimpleFeatureType不在其界限内 解决&#xff1a; 在引用处将org.opengis.feature.simple.SimpleFeatureType 改为 org.geotools.api.feature.simple.SimpleFeatureType

Web JavaScript

目录 1 前言2 原生js常见用法2.1 弹窗操作2.2 for循环操作2.3 打印日志操作2.4 获取页面值操作2.5 判空操作2.6 修改页面内容操作2.7 网页版计算器制作 3 外部js常见用法4 总结 1 前言 JavaScript 是一种脚本&#xff0c;一门编程语言&#xff0c;它可以在网页上实现复杂的功能…

jvm面试题目补充

jdk&jre Java程序设计语言、Java虚拟机、Java API类库这三部分统称为JDK&#xff08;Java Development Kit&#xff09;。 把Java API类库中的Java SE API子集 [1] 和Java虚拟机这两部分统称为JRE&#xff08;Java Runtime Environment&#xff09;&#xff0c;JRE是支持…

缓存一致性问题的解决策略

缓存一致性问题的背景和概念介绍 在一个系统中&#xff0c;我们通常使用数据库来存储数据&#xff0c;以保证数据的持久性。但是&#xff0c;由于数据库的读写速度相对较慢&#xff0c;如果每次请求都直接访问数据库&#xff0c;会降低系统的响应速度。为了提高系统的性能&…

进行模型测量这种量出来坡面的是平面面积还是真实面积?

斜面面积&#xff0c;不是表面积。 DasViewer是由大势智慧自主研发的免费的实景三维模型浏览器,采用多细节层次模型逐步自适应加载技术,让用户在极低的电脑配置下,也能流畅的加载较大规模实景三维模型,提供方便快捷的数据浏览操作。 #DasViewer##实景三维##三维重建##三维模型…

产品经理学习-产品运营《什么是SOP》

目录 什么是SOP 如何执行SOP 执行SOP的重点 什么是SOP SOP就是项目流程操作的说明书 日常工作中的例行操作&#xff1a; 例行操作是指&#xff0c;在每一天&#xff0c;针对每一个用户&#xff0c;在每个项目之中&#xff0c;都必须完成的操作&#xff0c;这些必须完成的操…