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

文章目录

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

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

一、684、冗余连接 I

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

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

  程序如下

class Solution {
private:int n = 200005;		// 节点数量 200000vector<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;		// 节点数量 1005int 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;		// 节点数量 200000vector<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;		// 节点数量 1005int 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.rhkb.cn/news/264619.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【iOS ARKit】ARWorldMap

ARWorldMap 用于存储 ARSession 检测扫描到的空间信息数据&#xff0c;包括地标&#xff08;Landmark&#xff09;、特征点&#xff08;Feature Point&#xff09;、平面&#xff08;Plane&#xff09;等&#xff0c;以及使用者的操作信息&#xff0c;如使用者添加的 ARAnchor …

【非比较排序】计算排序算法

目录 CountSort计数排序 整体思想 图解分析 代码实现 时间复杂度&优缺分析 CountSort计数排序 计数排序是一种非比较排序&#xff0c;不需要像前面的排序一样去比较。 计数排序的特性总结&#xff1a; 1. 计数排序在数据范围集中时&#xff0c;效率很高&#xff0c;但…

golang gin单独部署vue3.0前后端分离应用

概述 因为公司最近的项目前端使用vue 3.0&#xff0c;后端api使用golang gin框架。测试通过后&#xff0c;博文记录&#xff0c;用于备忘。 步骤 npm run build&#xff0c;构建出前端项目的dist目录&#xff0c;dist目录的结构具体如下图 将dist目录复制到后端程序同级目录…

Unity中URP下实现水体(水面高光)

文章目录 前言一、实现高光反射原理1、原理&#xff1a;2、公式&#xff1a; 二、实现1、定义 _SpecularColor 作为高光反射的颜色2、定义 _SpecularIntensity 作为反射系数&#xff0c;控制高光反射的强度3、定义 _Smoothness 作为高光指数&#xff0c;用于模型高光范围4、模拟…

紫外-可见吸收光谱法(UV-Vis)是最常用吸收光谱技术 市场持续扩大

紫外-可见吸收光谱法&#xff08;UV-Vis&#xff09;是最常用吸收光谱技术 市场持续扩大 紫外-可见吸收光谱法&#xff0c;也称为紫外-可见分光光度法&#xff0c;简称UV-Vis&#xff0c;利用样品分子在紫外和可见光激发下产生电子能级跃迁形成的吸收光谱&#xff0c;对元素进行…

Day 2.exec函数族和线程的基本概念、相关函数接口

exec函数族 extern char **environ; int execl(const char *path, const char *arg, ... /* (char *) NULL */); int execlp(const char *file, const char *arg, ... /* (char *) NULL */); int execle(const…

9.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-接管游戏连接服务器的操作

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;游戏底层功能对接类GameProc的实现 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;44c54d30370d3621c1e9ec3d7fa1e2a0…

全球游戏市场回暖,Flat Ads推动海外获客增长

摘要:热门游戏品类分析,解读新兴市场与赛道 近日,中国音数协游戏工委发布了《2023年中国游戏出海研究报告》,据报告数据显示,2023年,全球游戏市场规模11773.79亿元,同比增长6.00%,呈现增长回暖趋势。 图源:伽马数据 1.SLG和RPG游戏热度居高不下,休闲游戏增长势头强劲 目前,S…

Java四大引用详解:强引用、软引用、弱引用、虚引用

在JDK1.2以前的版本中&#xff0c;当一个对象不被任何变量引用&#xff0c;那么程序就无法再使用这个对象。也就是说&#xff0c;只有对象处于可触及状态&#xff0c;程序才能使用它。这就像在商店购买了某样物品后&#xff0c;如果有用就一直保留它&#xff0c;否则就把它扔到…

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

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

基于springboot+vue的音乐网站(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

Java优先级队列--堆

目录 1. 优先级队列 1.1 概念 2.优先级队列的模拟实现 2.1 堆的概念 2.2 堆的存储方式 2.3 堆的创建 2.3.1 堆向下调整 2.3.2 堆的创建 2.3.3 建堆的时间复杂度 2.4 堆的插入与删除 2.4.1 堆的插入 2.4.2 堆的删除 2.5 用堆模拟实现优先级队列 3.常用接口介绍 3…

【Excel PDF 系列】POI + iText 库实现 Excel 转换 PDF

你知道的越多&#xff0c;你不知道的越多 点赞再看&#xff0c;养成习惯 如果您有疑问或者见解&#xff0c;欢迎指教&#xff1a; 企鹅&#xff1a;869192208 文章目录 前言转换前后效果引入 pom 配置代码实现 前言 最近遇到生成 Excel 并转 pdf 的需求&#xff0c;磕磕碰碰总…

初学学习408之数据结构--数据结构基本概念

初学学习408之数据结构我们先来了解一下数据结构的基本概念。 数据结构&#xff1a;是相互之间存在一种或多种特定关系的数据元素的集合。 本内容来源于参考书籍《大话数据结构》与《王道数据结构》。除去书籍中的内容&#xff0c;作为初学者的我会尽力详细直白地介绍数据结构的…

元学习(meta-learning)的通俗解释

目录 1、什么是元学习 2、元学习还可以做什么 3、元学习是如何训练的 1、什么是元学习 meta-learning 的一个很经典的英文解释是 learn to learn&#xff0c;即学会学习。元学习是一个很宽泛的概念&#xff0c;可以有很多实现的方式&#xff0c;下面以目标检测的例子来解释…

JSON简介以及如何在Python中使用JSON

什么是JSON&#xff1f; JSON是"JavaScript Object Notation"的简称&#xff0c;是一种数据交换格式 JSON格式 假设我们有一个对象&#xff0c;这个对象有两个属性&#xff1a;“name”跟“age”。 在JSON中是这样表达的&#xff1a; { "name":"男孩…

基于JAVA springboot+mybatis智慧生活分享平台设计和实现

基于JAVA springbootmybatis智慧生活分享平台设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 可定制系统 欢迎点赞 收藏 …

FL Studio Producer Edition2024中文进阶版Win/Mac

FL Studio Producer Edition&#xff0c;特别是其【中文进阶版 Win/Mac】&#xff0c;是数字音乐制作领域中的一款知名软件。它为广大音乐制作人、声音工程师以及音乐爱好者提供了一个从音乐构思到最终作品发布的完整解决方案。这个版本特别为中文用户优化&#xff0c;并兼容W…

Leetcode——hot3最长连续序列

最长连续序列 class Solution {public int longestConsecutive(int[] nums) {if(nums.length 0 || nums.length 1){return nums.length;}Arrays.sort(nums);int count 1;int max 1;for(int i 0; i < nums.length - 1; i){if(nums[i1] - nums[i] 1){count;if(count &…