【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II

LeetCode685. 冗余连接 II

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

预备知识

有向图并集查找使用的2个前提:

前提一:所有的点出度为1或为0。
前提二:无环。
以当前节点为起点的最长路径终点,简称最长终点。如:两条边 A B → \overrightarrow{AB} AB B C → \overrightarrow{BC} BC ,A的最长终点是C。

有向树性质

性质一:任意节点都能访问自己所有后代,且不能访问自己后代之外的节点。
性质二:如果删除的边不是指向某节点的祖先,则根节点一定能访问此节点。

暴力解法

枚举删除的边,时间复杂度O(n);判断余下的边是否是有根树,时间复杂度O(n^n)。

深度优先判断有根树

前提三该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。 ⟺ \iff 只有一个节点的入度为0,此节点为根。其它节点入度为1。
前提四该树只有一个根节点,所有其他节点都是该根节点的后继。 ⟺ \iff 根节点可以访问所有的节点。通过一条边直接访问的节点是根节点的后继。通过两条边访问的节点是后继的后继 ⋯ \cdots
如果有环一定不行,n-1条边,如果有重复节点则无法访问n-1个新节点。所以访问了重复的节点直接返回。

核心代码

class Solution {
public:vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {m_c = edges.size();for (int i = m_c-1 ; i >=0 ;i--){vector<vector<int>> vNeiBo(m_c);for (int j = 0; j < m_c; j++){if (j != i){vNeiBo[edges[j][0] - 1].emplace_back(edges[j][1] - 1);}}if (Is(vNeiBo)){				return edges[i];}}return {};}bool Is(const vector<vector<int>>& vNeiBo)const{vector<int> vInDeg(m_c);for (const auto& v : vNeiBo){for (const auto& to : v){vInDeg[to]++;}			}for (int i = 0; i < m_c; i++){if (0 == vInDeg[i]){vector<int> vVis(m_c);DFS(vVis, vNeiBo, i);return m_c == std::accumulate(vVis.begin(), vVis.end(), 0);}}return false;}void DFS(vector<int>& vVis, const vector<vector<int>>& vNeiBo,int cur)const{if (vVis[cur]){return;}vVis[cur] = 1;for (const auto& next : vNeiBo[cur]){DFS(vVis, vNeiBo, next);}}int m_c;};

测试用例

template<class T,class T2>
void Assert(const T& t1, const T2& t2)
{assert(t1 == t2);
}template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{if (v1.size() != v2.size()){assert(false);return;}for (int i = 0; i < v1.size(); i++){Assert(v1[i], v2[i]);}}int main()
{vector<vector<int>> edges;{Solution sln;edges = { {5,2},{5,1},{3,1},{3,4},{3,5} };auto res = sln.findRedundantDirectedConnection(edges);Assert({ 3,1 }, res);}{Solution sln;edges = { {1, 2}, { 2,3 }, { 3,1 }, { 4,1 } };auto res = sln.findRedundantDirectedConnection(edges);Assert({ 3,1 }, res);}{Solution sln;edges = { {1,2},{1,3},{2,3} };auto res = sln.findRedundantDirectedConnection(edges);Assert({ 2,3 }, res);}{Solution sln;edges = { {1,2},{2,3},{3,4},{4,1},{1,5} };auto res = sln.findRedundantDirectedConnection(edges);Assert({ 4,1 }, res);}{Solution sln;edges = { {2,1},{3,1},{4,2},{1,4} };auto res = sln.findRedundantDirectedConnection(edges);Assert({ 2,1 }, res);}{Solution sln;edges = { {4,2},{1,5},{5,2},{5,3},{2,4} }		;auto res = sln.findRedundantDirectedConnection(edges);Assert({ 4,2 }, res);}
}

并集查找

所有边反向,即:根节出度为0,其它节点出度为1。所有节点的最长终点是都是根节点。即判定一:所有节点的最终节点相同。
有向树是判断一,无需证明。下面来证明判断一一定是有向树,判定一符合前提四无需证明,只需证明判定一符合前提三。
{ n − 1 个非根节点最终目的地是根节点,说明这些定点的出度至少为 1 。 总共 n − 1 条边。 → { 根节点出度为 0 其它节点出度为 1 \begin{cases} n-1个非根节点最终目的地是根节点,说明这些定点的出度至少为1。\\ 总共n-1条边。\\ \end{cases}\rightarrow \begin{cases}根节点出度为0\\ 其它节点出度为1 \end{cases} {n1个非根节点最终目的地是根节点,说明这些定点的出度至少为1总共n1条边。{根节点出度为0其它节点出度为1

简化

n-1条边,如果无环,则说明访问了n-1个新节点。且只有一个连通区域,否则顶点更多。故:
无环没有出度为2的定点 ⟺ \iff 有向树

代码

class CUnionFindDirect
{
public:CUnionFindDirect(int iSize){m_vRoot.resize(iSize);iota(m_vRoot.begin(), m_vRoot.end(), 0);}void Connect(bool& bConflic, bool& bCyc, int iFrom, int iTo){bConflic = bCyc = false;if (iFrom != m_vRoot[iFrom]){bConflic = true;}Fresh(iTo);if (m_vRoot[iTo] == iFrom){bCyc = true;}if (bConflic || bCyc){return;}m_vRoot[iFrom] = m_vRoot[iTo];}
private:int Fresh(int iNode){if (m_vRoot[iNode] == iNode){return iNode;}return m_vRoot[iNode] = Fresh(m_vRoot[iNode]);}vector<int> m_vRoot;
};class Solution {
public:vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {m_c = edges.size();for (int i = m_c-1 ; i >=0 ;i--){			if (Is(edges,i)){				return edges[i];}}return {};}bool Is(const vector<vector<int>>& edges,int i )const{CUnionFindDirect uf(m_c);for (int j = 0; j < m_c; j++){if (j != i){bool b2 = false, bCyc = false;uf.Connect(b2,bCyc,edges[j][1] - 1, edges[j][0] - 1);if (b2 || bCyc){return false;}}}return true;}int m_c;};

特殊情况

如果有环,计算最长终点,可能会死循环。所以要提前判断。有环 ⟺ \iff B的最长终点是A,发现新边 A B → \overrightarrow{AB} AB
必要条件无需证明环是: B → A → B 。 必要条件无需证明环是:B \rightarrow A \rightarrow B 。 必要条件无需证明环是:BAB
现在证明充分条件:
由于是环,所以B一定能到A,假定增加新边 A B → \overrightarrow{AB} AB 前,B的最长终点是C,那么A能访问C,也就是A出度至少为1。加上 A B → \overrightarrow{AB} AB 出度就成了2。和前提三矛盾。

优化

有向森林B的父节点是C,增加的边 A B → \overrightarrow{AB} AB 。有以下两种情况:
一,无环。如果B不是A的祖先,则B无法访问A,增加新边后,就无法形成环。存在入度为2的顶点
1.1,和森林的某边重合。删除任意一边。
在这里插入图片描述

1.2,前向边,A是祖先,B是后代(A能访问B)。
在这里插入图片描述
如果先发现AB,后发现CB则是横叉边。
1.3,横叉变。A和B都无法到达他们的公共祖先,所以不会有环。
在这里插入图片描述
如果先发现AB,后发现CB则是前向边。
总结: 根据性质二,B不会是A祖先,所以1.2和1.3删除CB不会影响根节点访问A。

二,后向边,B是祖先,A是后代,B能访问A。增加新边后,A能访问B。形成了环。
2.1,如果B是根节点,则这个环上所有节点都能访问B,也就是能访问所有节点。删除任意一边后,会形成新树。不存在入度为2的顶点
在这里插入图片描述

,2.2 如果B不是根节点。不包括 C B → \overrightarrow{CB} CB A B → \overrightarrow{AB} AB 如果C是根节点的后代,则删除 A B → \overrightarrow{AB} AB 可以形成新树 。如果A是根节点的后代,则删除 C B → \overrightarrow{CB} CB ,可形成新树 。存在入度为2的顶点

在这里插入图片描述

无论是发现环,还是发现入度为2,还是同时发现环和入度为2,都只能删除折线。
处理方法一: { 删除 C B → 不通过 C B → ,根节点能到达 A 。 删除 A B → 不通过 A B → ,根节点能到达 C 。 处理方法一:\begin{cases} 删除\overrightarrow{CB} & 不通过\overrightarrow{CB},根节点能到达A。\\ 删除\overrightarrow{AB} &不通过\overrightarrow{AB},根节点能到达C。\\ \end{cases} 处理方法一:{删除CB 删除AB 不通过CB ,根节点能到达A不通过AB ,根节点能到达C

总结,5种情况合并两种:
{ 删除环中任意一边(最后一边) 不存在入度为 2 的定点 处理方法一 o t h e r \begin{cases} 删除环中任意一边(最后一边) & 不存在入度为2的定点 \\ 处理方法一 & other \\ \end{cases} {删除环中任意一边(最后一边)处理方法一不存在入度为2的定点other

代码

class CUnionFindDirect
{
public:CUnionFindDirect(int iSize){m_vRoot.resize(iSize);iota(m_vRoot.begin(), m_vRoot.end(), 0);}void Connect(bool& bConflic, bool& bCyc, int iFrom, int iTo){bConflic = bCyc = false;if (iFrom != m_vRoot[iFrom]){bConflic = true;}Fresh(iTo);if (m_vRoot[iTo] == iFrom){bCyc = true;}if (bConflic || bCyc){return;}m_vRoot[iFrom] = m_vRoot[iTo];}int GetMaxDest(int iFrom){Fresh(iFrom);return m_vRoot[iFrom];}	
private:int Fresh(int iNode){if (m_vRoot[iNode] == iNode){return iNode;}return m_vRoot[iNode] = Fresh(m_vRoot[iNode]);}vector<int> m_vRoot;
};class Solution {
public:vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {m_c = edges.size();vector<int> vParent(m_c, -1);int iParentOld = -1;for (int j = 0; j < m_c; j++){if (vParent[edges[j][1] - 1] >= 0){iParentOld = vParent[edges[j][1] - 1];}vParent[edges[j][1] - 1] = edges[j][0] - 1;}const int root = std::find(vParent.begin(), vParent.end(), -1) - vParent.begin();CUnionFindDirect uf(m_c);		int iCyc = -1;int i2 = -1;for (int j = 0; j < m_c; j++){bool b2 = false, bCyc = false;uf.Connect(b2, bCyc, edges[j][1] - 1, edges[j][0] - 1);if (b2){i2 = j;}if (bCyc){iCyc = j ;}}if (-1 != i2){const int dest = uf.GetMaxDest(edges[i2][1] - 1);return (root == dest) ? edges[i2] : vector<int>{ iParentOld + 1, edges[i2][1] };}return edges[iCyc];}int m_c;
};

注意

一,所有边都加到并集查找才判断最长终点。
二,由于冲突边和环边会忽略。所以可能出现 忽略环边时,让冲突边也消失。碰巧可以按环处理。
在这里插入图片描述
BA和CD已经发现,CB未发现。发现AB是环边,忽略。由于AB被忽略,所以CB不是冲突边。

娱乐性优化

情况2.2进一步分析,无论何种发现顺序都是删除环冲突边:
2.2.1,同时发现环边和冲突边。环冲突边最后发现。删除冲突边。
2.2.2 先发现环边,环边是冲突边。无法发先冲突边。删除环边。
2.2.3先发现环边,环边不是冲突边。能发现冲突边。删除另外一条冲突边。
2.2.4 先发现冲突边,忽略冲突环边,无法发现环。删除冲突边。
2.2.5 先发现冲突边,忽略非环冲突边,能发现环。删除另外一条冲突边。
总结: { 删除冲突边 发现冲突边未发现环边 1 , 2.2.4 删除环边 发现环边未发现冲突边 2.1 , 2.22 删除冲突边(环边 ) 冲突边就是环边 2.2.1 删除另外一条冲突边 发现冲突边和环边两者不等 2.2.3 , 2.25 总结:\begin{cases} 删除冲突边 & 发现冲突边未发现环边 & 1,2.2.4 \\ 删除环边 & 发现环边未发现冲突边 &2.1, 2.22 \\ 删除冲突边(环边) & 冲突边就是环边 & 2.2.1 \\ 删除另外一条冲突边 & 发现冲突边和环边两者不等 & 2.2.3,2.25\\ \end{cases} 总结: 删除冲突边删除环边删除冲突边(环边)删除另外一条冲突边发现冲突边未发现环边发现环边未发现冲突边冲突边就是环边发现冲突边和环边两者不等1,2.2.42.1,2.222.2.12.2.3,2.25

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

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

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

相关文章

离线数仓(二)【用户行为日志采集平台搭建】

用户行为日志采集平台搭建 1、用户行为日志概述 用户行为日志的内容&#xff0c;主要包括用户的各项行为信息以及行为所处的环境信息。收集这些信息的主要目的是优化产品和为各项分析统计指标提供数据支撑。收集这些信息的手段通常为埋点。 目前主流的埋点方式&#xff0c;有代…

CleanMyMacX需要付费吗?多少钱?有哪些新功能

CleanMyMac X是一个付费应用程序**&#xff0c;需要许可证或订阅来解锁所有功能。不过&#xff0c;CleanMyMac X提供免费试用版供您访问其有限的功能。在试用模式下&#xff0c;用户可以使用部分功能进行体验&#xff0c;但这并非完全免费&#xff0c;因为某些功能会受到限制。…

怎样才能使网页跳转更加顺畅和自然?

网页跳转是用户在使用网页时经常遇到的操作&#xff0c;一个好的跳转设计可以提升用户体验&#xff0c;提高用户满意度。然而&#xff0c;有些网页的跳转设计却常常给用户带来不好的体验&#xff0c;比如页面加载缓慢、跳转速度慢、页面卡顿等问题。那么&#xff0c;怎样才能使…

在SpringBoot中@PathVariable与@RequestParam的区别

PathVariable GetMapping("/{userId}")public R<User> getUserById(PathVariable Long userId) {return userService.getUserById(userId);} // 根据id获取一条数据 function getStudentDataByIdAndDisplayInput(id) {// 发送 AJAX 请求$.ajax({url: /dorm/st…

Elasticsearch:将 IT 智能和业务 KPI 与 AI 连接起来 - 房间里的大象

作者&#xff1a;Fermi Fang 大象寓言的智慧 在信息技术和商业领导力的交叉点&#xff0c;蒙眼人和大象的古老寓言提供了一个富有洞察力的类比。 这个故事起源于印度次大陆&#xff0c;讲述了六个蒙住眼睛的人第一次遇到大象的故事。 每个人触摸大象的不同部位 —— 侧面、象牙…

Vue3快速上手(八) toRefs和toRef的用法

顾名思义&#xff0c;toRef 就是将其转换为ref的一种实现。详细请看&#xff1a; 一、toRef 1.1 示例 <script langts setup name"toRefsAndtoRef"> // 引入reactive,toRef import { reactive, toRef } from vue // reactive包裹的数据即为响应式对象 let p…

第三篇【传奇开心果系列】Python的文本和语音相互转换库技术点案例示例:pyttsx3实现语音助手经典案例

传奇开心果短博文系列 系列短博文目录Python的文本和语音相互转换库技术点案例示例系列 短博文目录一、项目背景和目标二、雏形示例代码三、扩展思路介绍四、与其他库和API集成示例代码五、自定义语音示例代码六、多语言支持示例代码七、语音控制应用程序示例代码八、文本转语音…

机器人初识 —— 定制AI

一、机器人设计难点 波士顿动力设计的机器人&#xff0c;尤其是其人形机器人Atlas和四足机器人Spot等产品&#xff0c;在技术上面临多重难点&#xff1a; 1. **动态平衡与稳定性**&#xff1a;双足或四足机器人在运动时需要维持极高的动态平衡&#xff0c;特别是在不平坦地面…

LiveGBS流媒体平台GB/T28181功能-自定义收流端口区间30000至30249UDP端口TCP端区间配置及相关端口复用问题说明

LiveGBS自定义收流端口区间30000至30249UDP端口TCP端区间配置及相关端口复用问题说明 1、收流端口配置1.1、INI配置1.2、页面配置 2、相关问题3、最少可以开放多少端口3.1、端口复用3.2、配置最少端口如下 4、搭建GB28181视频直播平台 1、收流端口配置 1.1、INI配置 可在lives…

基于Java SSM框架实现电影售票系统项目【项目源码】

基于java的SSM框架实现电影售票系统演示 SSM框架 当今流行的“SSM组合框架”是Spring SpringMVC MyBatis的缩写&#xff0c;受到很多的追捧&#xff0c;“组合SSM框架”是强强联手、各司其职、协调互补的团队精神。web项目的框架&#xff0c;通常更简单的数据源。Spring属于…

VNCTF2024misc方向部分wp

文章目录 sqlsharkLearnOpenGLez_msbOnlyLocalSql sqlshark tshark -r sqlshark.pcap -Y "http" -T fields -e frame.len -e http.file_data > data.txt不太像常规的盲注&#xff0c;一次性发送两条很类似的payload&#xff0c;比常规的多了一个least在判断passw…

如果端口显示被占用,无法重启,需要杀掉这些进程服务,再次重启即可

如果端口显示被占用&#xff0c;无法重启&#xff0c;需要杀掉这些进程服务&#xff0c;再次重启即可 jps显示当前有的服务 kill -9 要杀掉服务的对应数字

Eclipse - Reset Perspective

Eclipse - Reset Perspective 1. Window -> Perspective -> Reset Perspective2. Reset Perspective -> YesReferences 1. Window -> Perspective -> Reset Perspective 2. Reset Perspective -> Yes ​​​ References [1] Yongqiang Cheng, https://yo…

html的表单标签(上):form标签和input标签

表单标签 表单是让用户输入信息的重要途径。 用表单标签来完成与服务器的一次交互&#xff0c;比如你登录QQ账号时的场景。 表单分成两个部分&#xff1a; 表单域&#xff1a;包含表单元素的区域&#xff0c;用form标签来表示。表单控件&#xff1a;输入框&#xff0c;提交按…

.net和jar包windows服务部署

一.NetCore 1.创建启动脚本run_instal.bat,例如程序文件为ApiDoc.exe set serviceName"Apidoc Web 01" set serviceFilePath%~dp0ApiDoc.exe set serviceDescription"ApiDoc 动态接口服务 web 01"sc create %serviceName% BinPath%serviceFilePath% sc c…

【Java EE初阶十四】网络编程TCP/IP协议(一)

1. 网络编程 通过网络&#xff0c;让两个主机之间能够进行通信->就这样的通信来完成一定的功能&#xff0c;进行网络编程的时候&#xff0c;需要操作系统给咱们提供一组API&#xff0c;通过这些API来完成编程&#xff1b;API可以认为是应用层和传输层之间交互的路径&#xf…

考研证件照可以自己用手机拍吗?考研证件照p过可以通过审核吗?考研证件照有什么要求

一、考研证件照可以自己用手机拍吗 现在的智能手机相机技术先进&#xff0c;大多都配备了高像素摄像头&#xff0c;使得自拍照片的质量有了大幅提升。相较于传统的证件照拍摄&#xff0c;使用手机自拍考研证件照理论上是可行的。然而&#xff0c;考研证件照需要满足一定的规定…

如何根据需求理解CPU、SoC和MCU的区别

在当今数字化的世界中&#xff0c;我们经常听到关于CPU、SoC和MCU的名词&#xff0c;它们都是计算机科学和电子工程领域中的重要组成部分。然而&#xff0c;这三者之间存在着明显的区别。本文将深入探讨CPU&#xff08;中央处理器&#xff09;、SoC&#xff08;系统芯片&#x…

阿里云服务器部署配置选择全攻略

阿里云服务器配置怎么选择&#xff1f;根据实际使用场景选择&#xff0c;个人搭建网站可选2核2G配置&#xff0c;访问量大的话可以选择2核4G配置&#xff0c;企业部署Java、Python等开发环境可以选择2核8G配置&#xff0c;企业数据库、Web应用或APP可以选择4核8G配置或4核16G配…

【正点原子STM32连载】 第五十一章 T9拼音输入法实验 摘自【正点原子】APM32E103最小系统板使用指南

1&#xff09;实验平台&#xff1a;正点原子APM32E103最小系统板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/docs/boards/xiaoxitongban 第五…