Day62_补20250210_图论part6_108冗余连接|109.冗余连接II

news2025/2/11 9:35:56

Day62_20250210_图论part6_108冗余连接|109.冗余连接II

108冗余连接 【把题意转化为并查集问题】

题目

有一个图,它是一棵树,他是拥有 n 个节点(节点编号1到n)和 n - 1 条边的连通无环无向图(其实就是一个线形图),如图:

现在在这棵树上的基础上,添加一条边(依然是n个节点,但有n条边),使这个图变成了有环图,如图:

先请你找出冗余边,删除后,使该图可以重新变成一棵树。

输入示例
3
1 2
2 3
1 3
输出示例
1 3

思路

  • 思路
    • 题意转化为并查集
      • 当存在两个顶点处于同一个集合时,重复2次,说明有冗余边,删除最后一个。
      • 代码,初始化DisJoint,用join将两个顶点建立一个集合,然后用isSame判断两个不在1个集合中的顶点是否在一个集合中,并且判断两个在1个集合中的顶点是否重复加入了
  • 代码
    import java.util.*;
    public class Main{
        public static void main(String[] args){
            //输入
            Scanner scanner=new Scanner(System.in);
            int n=scanner.nextInt();
            DisJoint disJoint=new DisJoint(n+1);
            int[][] edges=new int[n][2];//m条边
        
            for(int i=0;i<n;i++){
                edges[i][0]=scanner.nextInt();
                edges[i][1]=scanner.nextInt();
            }
            for(int i=0;i<n;i++){
                int u=edges[i][0];
                int v=edges[i][1];
            
                //先检查是否冗余:如果2个节点在同一集合中,【本身已经连通,才会输出冗余边】
                if(disJoint.isSame(u,v)){
                    System.out.println(u+" "+v);
                    return;
                }
                //不冗余的话将2个边加入到同一集合中
                disJoint.join(u,v);
            }
        }
    }
    class DisJoint{
        private int[] father;//父节点
        //初始化
        public DisJoint(int N){
            father=new int[N];
            for(int i=0;i<N;i++){
                father[i]=i;
            }
        }
        //寻找根节点
        public int find(int n){
            return n==father[n]?n:(father[n]=find(father[n]));
        }
        //将2个节点加入到同一个集合中
        public void join(int n,int m){
            n=find(n);
            m=find(m);
            if(n==m) return;
            father[m]=n;
        }
        //判断2个节点是否在同一个集合中
        public boolean isSame(int n,int m){
            return find(n)==find(m);
        }
    }
    
    

总结

  • 注意,题目中冗余的边的个数仅为1条。
  • 思路
    • 将2

109.冗余连接II 【难度上升】

题目

有一种有向树,该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。有向树拥有 n 个节点和 n - 1 条边。如图:

现在有一个有向图,有向图是在有向树中的两个没有直接链接的节点中间添加一条有向边。如图:

输入一个有向图,该图由一个有着 n 个节点(节点编号 从 1 到 n),n 条边,请返回一条可以删除的边,使得删除该条边之后该有向图可以被当作一颗有向树。

输入描述

第一行输入一个整数 N,表示有向图中节点和边的个数。

后续 N 行,每行输入两个整数 s 和 t,代表这是 s 节点连接并指向 t 节点的单向边

输出描述

输出一条可以删除的边,若有多条边可以删除,请输出标准输入中最后出现的一条边。

输入示例
3
1 2
1 3
2 3
输出示例
2 3
提示信息

在删除 2 3 后有向图可以变为一棵合法的有向树,所以输出 2 3

数据范围:

1 <= N <= 1000.

思路

  • 思路
    • 与上一题的区别是,有向图
      • 有向树,根节点入度为0,其他节点入度都为1。
    • 核心:如果我们找到入度为2的点,那么删一条指向该节点的边就行了。
    • 注意特殊情况:
      • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
        • 节点3的入度为2,但在删除边的时候,只能删(1到3),如果删(4-3),就不是有向树了(找不到根节点)。
        • 如果发现入度为2的节点,判断删除哪一条边,才能成为有向树,如果是删哪个都可以,优先删除顺序靠后的边。
      • 如果没有入度为2的点,说明图中有环了(有向环)。
        • 删除构成环的边就可以了。
  • 伪代码
    • 将每条边记录下来,并统计入度。
    • 情况1和情况2,从后向前遍历,如果有入度为2的情况,删除一条边后判断这个图是一个图,那么这条边是答案。如果删除哪条边都可以成为树,就删除最后那一条。
    • 如果没有入度为2的情况,一定有有向环,找到构成环的边就是要删除的边
  • 代码
    import java.util.*;
    
    public class Main {
        //并查集Disjoint类
        static class Disjoint {
            private int[] father;
    
            public Disjoint(int n) {
                father = new int[n + 1];
                for (int i = 1; i <= n; i++) {
                    father[i] = i;
                }
            }
    
            public int find(int n) {
                if (father[n] != n) {
                    father[n] = find(father[n]); // 路径压缩
                }
                return father[n];
            }
    
            public void join(int n, int m) {
                father[find(n)] = find(m);
            }
    
            public boolean isSame(int n, int m) {
                return find(n) == find(m);
            }
        }
    
        public static void main(String[] args) {
            //输入
            Scanner scanner = new Scanner(System.in);//scanner
            int n = scanner.nextInt();
            int[][] edges = new int[n][2]; //边
            int[] inDegree = new int[n + 1]; //入度
            Integer doubleInNode = null;//存储入度为2的节点
    
            // 读取输入并统计入度
            for (int i = 0; i < n; i++) {
                edges[i][0] = scanner.nextInt();
                edges[i][1] = scanner.nextInt();
                inDegree[edges[i][1]]++;
                //如果度为2,记录该节点
                if (inDegree[edges[i][1]] == 2) doubleInNode = edges[i][1]; 
            }
    
            //case1:存在入度为2的节点
            //找到2条指向doubleNode的边,存入candidates[][]
            if (doubleInNode != null) {
                int[][] candidates = new int[2][2];
                int index = 0;
                for (int i = 0; i < n; i++) {
                    if (edges[i][1] == doubleInNode) {
                        candidates[index++] = edges[i];
                        if (index == 2) break;
                    }
                }
    
                // 先尝试删除第2条边(candidates[1]),如果删除后形成树,输出candidates[1],否则输出candidates[0]
                if (isTreeAfterRemoving(edges, candidates[1], n)) {
                    System.out.println(candidates[1][0] + " " + candidates[1][1]);
                } else {
                    System.out.println(candidates[0][0] + " " + candidates[0][1]);
                }
                return;
            }
    
            //case2:不存在度为2(环),找出最后一条导致环的边
            int[] lastEdge = getLastEdgeToRemove(edges, n);
            System.out.println(lastEdge[0] + " " + lastEdge[1]);
        }
    
        // 方法 1:删除某条边后是否形成树
        private static boolean isTreeAfterRemoving(int[][] edges, int[] excludeEdge, int n) {
            Disjoint disjoint = new Disjoint(n);//检查环
            for (int i = 0; i < n; i++) {
                // **优化点:直接比较两个值,而不是 Arrays.equals**
                //if当前边是要删除的边(2个点),继续(continue);
                if (edges[i][0] == excludeEdge[0] && edges[i][1] == excludeEdge[1]) continue;
                //检查是否形成环
                //如果起点和终点已经在同一个集合,说明形成了环
                if (disjoint.isSame(edges[i][0], edges[i][1])) {
                    return false; 
                }
                //合并2个节点
                disjoint.join(edges[i][0], edges[i][1]);
            }
            return true;
        }
    
        // 方法 2:找到最后出现的环边
        //在有向图中找到最后一条导致环的边,并返回该边
    
        private static int[] getLastEdgeToRemove(int[][] edges, int n) {
            Disjoint disjoint = new Disjoint(n);
            int[] lastEdge = null;//记录最后一条导致环的边
    
    
            for (int i = 0; i < n; i++) {
                //检查是否形成环
                //如果起点和终点已经在同一集合,形成了环
                if (disjoint.isSame(edges[i][0], edges[i][1])) {
                    lastEdge = edges[i]; //更新存储的边
                } else {
                    //合并到1个集合中
                    disjoint.join(edges[i][0], edges[i][1]);
                }
            }
            return lastEdge;
        }
    }
    
    

总结

  • 这题好难,难在怎么写2个调用方法的代码

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

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

相关文章

kafka消费端之消费者协调器和组协调器

文章目录 概述回顾历史老版本获取消费者变更老版本存在的问题 消费者协调器和组协调器新版如何解决老版本问题再均衡过程**第一阶段CFIND COORDINATOR****第二阶段&#xff08;JOINGROUP&#xff09;**选举消费组的lcader选举分区分配策略 第三阶段&#xff08;SYNC GROUP&…

IDEA升级出现问题Failed to prepare an update Temp directory inside installation

IDEA升级出现问题"Failed to prepare an update Temp directory inside installation…" 问题来源&#xff1a; 之前修改了IDEA的默认配置文件路径&#xff0c;然后升级新版本时就无法升级&#xff0c;提示"Failed to prepare an update Temp directory insid…

十款开源的论坛建站工具

以下是十款开源的论坛建站工具&#xff0c;它们各具特色&#xff0c;能够满足不同用户的需求&#xff1a; Discuz!&#xff08;Crossday Discuz! Board&#xff09; 特点&#xff1a;基础架构采用web编程组合PHPMySQL&#xff0c;用户可以在不需要任何编程的基础上&#xff0c;…

vue学习6

1. 智慧商城 1. 路由设计配置 单个页面&#xff0c;独立展示的&#xff0c;是一级路由 2.二级路由配置 规则&组件配置导航链接配置路由出口 <template><div id"app"><!--二级路由出口--><router-view></router-view><van-…

线程池以及日志、线程总结

一、线程池以及日志 1、基础线程池写法 主线程在main函数中构建一个线程池&#xff0c;初始化(Init)后开始工作(Start) 此时线程池中每个线程都已经工作起来了&#xff0c;只是任务队列中任务为空&#xff0c;所有线程处于休眠状态(通过线程同步中的条件变量实现&#xff0c…

Vue 响应式渲染 - 过滤应用

Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue响应式渲染综合 - 过滤应用 目录 过滤应用 引入vue Vue设置 设置页面元素 模糊查询过滤实现 函数表达式实现 总结 过滤应用 综合响应式渲染做一个输入框&#xff0c;用来实现&#xff1b;搜索输入框关键词符合列表。…

【ThreeJS Basics 1-3】Hello ThreeJS,实现第一个场景

文章目录 环境创建一个项目安装依赖基础 Web 页面概念解释编写代码运行项目 环境 我的环境是 node version 22 创建一个项目 首先&#xff0c;新建一个空的文件夹&#xff0c;然后 npm init -y , 此时会快速生成好默认的 package.json 安装依赖 在新建的项目下用 npm 安装依…

深入理解动态代理

为什么需要动态代理 对于代码的增强逻辑我们是清楚具体实现的,一种方式是增强逻辑作为委托类,被其他业务类调用, 这样会有很多重复代码,而且,当需要根据动态参数来决定增强逻辑时,重复代码会更多,逻辑会更不清晰 二,也是动态代理产生的原始需求,解决类爆照问题, 所以…

Cherry Studio之DeepSeek联网/本地,建属于自己的AI助理!

上一篇文章&#xff0c;讲了DeepSeek-R1部署到本地的方法。这一篇文章&#xff0c;我们让DeepSeek再一次升级&#xff0c;通过图形化界面来交互&#xff0c;从而变成我们的AI助理&#xff0c;让DeepSeek R1发挥最大实力&#xff01; 首选需要借助硅基流动的API接口&#xff0c…

IGBT的两级关断

IGBT&#xff08;绝缘栅双极型晶体管&#xff09;的两级关断&#xff08;Two-stage turn-off&#xff09;是一种优化关断过程的方法&#xff0c;主要用于减少关断时的电压过冲和dv/dt&#xff08;电压变化率&#xff09;过高的问题&#xff0c;特别是在大功率应用中&#xff08…

【STM32】ADC

本次实现的是ADC实现数字信号与模拟信号的转化&#xff0c;数字信号时不连续的&#xff0c;模拟信号是连续的。 1.ADC转化的原理 模拟-数字转换技术使用的是逐次逼近法&#xff0c;使用二分比较的方法来确定电压值 当单片机对应的参考电压为3.3v时&#xff0c;0~ 3.3v(模拟信号…

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 0基础…

Chapter3:结构化程序设计

参考书籍&#xff1a;《C#边做边学》&#xff1b; 3.结构化程序设计 3.1 结构化程序设计的3种基本结构 顺序结构&#xff1a;先执行 A {\rm A} A语句&#xff0c;再执行 B {\rm B} B语句&#xff0c;两者是顺序执行的关系&#xff1b; 选择结构&#xff1a;根据所定选择条件为…

白话文实战Nacos(保姆级教程)

前言 上一篇博客 我们创建好了微服务项目,本篇博客来体验一下Nacos作为注册中心和配置中心的功能。 注册中心 如果我们启动了一个Nacos注册中心,那么微服务比如订单服务,启动后就可以连上注册中心把自己注册上去,这过程就是服务注册。每个微服务,比如商品服务都应该注册…

智能理解 PPT 内容,快速生成讲解视频

当我们想根据一版 PPT 制作出相对应的解锁视频时&#xff0c;从撰写解锁词&#xff0c;录制音频到剪辑视频&#xff0c;每一个环节都需要投入大量的时间和精力&#xff0c;本方案将依托于阿里云函数计算 FC 和百炼模型服务&#xff0c;实现从 PPT 到视频的全自动转换&#xff0…

IEC61850标准下的数据和数据模型服务的详细介绍

目录 一、摘要 二、概述 三、详细介绍 1、读服务器目录(GetServerDirectory) 2、读逻辑设备目录(GetLogicalDeviceDirectory) 3、读逻辑节点目录(GetLogicalNodeDirectory) 4、读全部数据值(GetAllDataValues) 5、读数据值(GetDataValues) 6、设置数据值(SetDataValues…

R语言LCMM多维度潜在类别模型流行病学研究:LCA、MM方法分析纵向数据

全文代码数据&#xff1a;https://tecdat.cn/?p39710 在数据分析领域&#xff0c;当我们面对一组数据时&#xff0c;通常会有已知的分组情况&#xff0c;比如不同的治疗组、性别组或种族组等&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 然而&#xff0c;…

5. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--微服务基础工具与技术--Nacos

一、什么是Nacos Nacos 是阿里巴巴开源的一款云原生应用基础设施&#xff0c;它旨在简化微服务架构中服务治理和配置管理的复杂性。通过 Nacos&#xff0c;服务在启动时可以自动注册&#xff0c;而其他服务则可以通过名称来查找并访问这些注册好的实例。同时&#xff0c;Nacos…

VUE项目中实现权限控制,菜单权限,按钮权限,接口权限,路由权限,操作权限,数据权限实现

VUE项目中实现权限控制&#xff0c;菜单权限&#xff0c;按钮权限&#xff0c;接口权限&#xff0c;路由权限&#xff0c;操作权限&#xff0c;数据权限实现 权限系统分类&#xff08;RBAC&#xff09;引言菜单权限按钮权限接口权限路由权限 菜单权限方案方案一&#xff1a;菜单…

【网络安全】服务器安装Docker及拉取镜像教程

文章目录 1. 安装 Docker2. 拉取镜像3. 运行 Ubuntu 容器4. 执行相关操作5. 退出并停止容器1. 安装 Docker # 更新软件包索引 sudo apt update# 安装必要的依赖 sudo apt install -y ca-certificates curl gnupg