详细讲解如何求解「内向基环森林」问题

题目描述

这是 LeetCode 上的 「2876. 有向图访问计数」 ,难度为 「困难」

Tag : 「基环森林」、「内向基环树」、「拓扑排序」、「图」、「BFS」

现有一个有向图,其中包含 n 个节点,节点编号从 0n - 1。此外,该图还包含了 n 条有向边。

给你一个下标从 0 开始的数组 edges,其中 edges[i] 表示存在一条从节点 i 到节点 edges[i] 的边。

想象在图上发生以下过程:

你从节点 x 开始,通过边访问其他节点,直到你在 此过程 中再次访问到之前已经访问过的节点。

返回数组 answer 作为答案,其中 answer[i] 表示如果从节点 i 开始执行该过程,你可以访问到的不同节点数。

示例 1: alt

输入:edges = [1,2,0,0]

输出:[3,3,3,4]

解释:从每个节点开始执行该过程,记录如下:
- 从节点 0 开始,访问节点 0 -> 1 -> 2 -> 0 。访问的不同节点数是 3 。
- 从节点 1 开始,访问节点 1 -> 2 -> 0 -> 1 。访问的不同节点数是 3 。
- 从节点 2 开始,访问节点 2 -> 0 -> 1 -> 2 。访问的不同节点数是 3 。
- 从节点 3 开始,访问节点 3 -> 0 -> 1 -> 2 -> 0 。访问的不同节点数是 4 。

示例 2: alt

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

输出:[5,5,5,5,5]

解释:无论从哪个节点开始,在这个过程中,都可以访问到图中的每一个节点。

提示:


内向基环森林 + 拓扑排序

根据题意,共 n 个点,n 条边,利用 edges,将 iedges[i] 连有向边,可知每个点有唯一的出边,因此这是一张可能包含多棵「内向基环树」的「基环森林」。

基环树是指其具有 个点 条边的联通块,而「内向」是指树中任意节点有且只有一条出边,对应的「外向」是指树中任意节点有且只有一条入边。

例如,左图内向,右图外向:

alt

显然,可根据当前节点是否在“环内”进行分情况讨论:

  • 对于「环内」节点来说,其答案为环节点个数;
  • 对于「环外」节点来说,直观感受应该是由环上节点转移而来。但由于本题给定的是「内向基环树」,因此我们需要对原图进行“反向”,然后从环内节点开始,进行 BFS ,从而更新其余非环节点答案。

具体的,我们使用如下思路进行求解:

  1. 创建大小为 n 的数组 in,进行入度统计;
  2. 根据入度进行「拓扑排序」,剩余满足 的点,为「环内」的点。我们可处理出每个点所在环的大小,环的大小为这些点的答案。处理过程中收集这些「环内」的点(将来要从它们出发,更新其他「环外」节点)
  3. 对原图进行“反向”,从收集好的「环内」点进行出发,运用 BFS 得出剩余点答案。

Java 代码:

class Solution {
    int N = 200010, M = N, idx = 0;
    int[] he = new int[N], e = new int[M], ne = new int[M];
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx++;
    }
    public int[] countVisitedNodes(List<Integer> edges) {
        int n = edges.size();
        int[] in = new int[n], ans = new int[n];
        for (int x : edges) in[x]++;
        Deque<Integer> d = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            if (in[i] == 0) d.addLast(i);
        }
        while (!d.isEmpty()) {
            int t = edges.get(d.pollFirst());
            if (--in[t] == 0) d.addLast(t);
        }
        // 处理环上的
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < n; i++) {
            if (in[i] == 0continue;
            List<Integer> list = new ArrayList<>();
            list.add(i);
            int j = edges.get(i), val = 1;
            while (j != i) {
                list.add(j);
                j = edges.get(j);
                val++;
            }
            for (int x : list) {
                set.add(x);
                in[x] = 0;
                ans[x] = val;
            }
        }
        // 建立反向图, 处理非环上的, 从环内点出发进行往外更新
        Arrays.fill(he, -1);
        for (int i = 0; i < n; i++) add(edges.get(i), i);
        for (int u : set) {
            int val = ans[u];
            Deque<Integer> de = new ArrayDeque<>();
            de.addLast(u);
            while (!de.isEmpty()) {
                int sz = de.size();
                while (sz-- > 0) {
                    int t = de.pollFirst();
                    ans[t] = val;
                    for (int i = he[t]; i != -1; i = ne[i]) {
                        int j = e[i];
                        if (ans[j] != 0continue;
                        de.addLast(j);
                    }
                }
                val++;
            }
        }
        return ans;
    }
}

C++ 代码:

class Solution {
public:
    int he[200010], e[200010], ne[200010], idx;
    void add(int a, int b) {
        e[idx] = b;
        ne[idx] = he[a];
        he[a] = idx++;
    }
    vector<intcountVisitedNodes(vector<int>& edges) {
        int n = edges.size();
        vector<intin(n, 0)ans(n, 0);
        for (int x : edges) in[x]++;
        queue<int> d;
        for (int i = 0; i < n; i++) {
            if (in[i] == 0) d.push(i);
        }
        while (!d.empty()) {
            int t = edges[d.front()];
            d.pop();
            if (--in[t] == 0) d.push(t);
        }
        set<int> s;
        for (int i = 0; i < n; i++) {
            if (in[i] == 0continue;
            vector<intlist;
            list.push_back(i);
            int j = edges[i], val = 1;
            while (j != i) {
                list.push_back(j);
                j = edges[j];
                val++;
            }
            for (int x : list) {
                s.insert(x);
                in[x] = 0;
                ans[x] = val;
            }
        }
        memset(he, -1sizeof(he));
        for (int i = 0; i < n; i++) add(edges[i], i);
        for (int u : s) {
            int val = ans[u];
            queue<int> de;
            de.push(u);
            while (!de.empty()) {
                int sz = de.size();
                while (sz-- > 0) {
                    int t = de.front();
                    de.pop();
                    ans[t] = val;
                    for (int i = he[t]; i != -1; i = ne[i]) {
                        int j = e[i];
                        if (ans[j] != 0continue;
                        de.push(j);
                    }
                }
                val++;
            }
        }
        return ans;
    }
};
  • 时间复杂度:统计入度复杂度为 ;拓扑排序复杂度为 ;统计「环内」节点答案复杂度为 ;统计「环外」答案复杂度为 。整体复杂度为
  • 空间复杂度:

最后

这是我们「刷穿 LeetCode」系列文章的第 No.2876 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

本文由 mdnice 多平台发布

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

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

相关文章

运动重定向:TeachNet

Vision-based Teleoperation of Shadow Dexterous Hand using End-to-End Deep Neural Network解析 摘要1. 简介2. Related Work2.1 基于视觉的无标记远程操作2.2 基于深度的3D手部姿势估计2.3 远程操作中的主从配对2.4 遥操作映射方法 3. 师生网络Joint angle lossConsistency…

图像置乱加密的破解方法

仅仅通过置乱的方式,是无法对图像进行安全加密的。 针对采用置乱方式加密,可以采用多对(明文、密文)推导出加密时所使用的置乱盒。 step1 :初始化 1、使用I表示明文,E表示密文,彼此间关系如下: 2、为了处理上的方便,把二维转换为一维(这里为了说明方便,实际上,大…

【油猴脚本】学习笔记

目录 新建用户脚本模板源注释 测试代码获取图标 Tampermonkey v4.19.0 原教程&#xff1a;手写油猴脚本&#xff0c;几分钟学会新技能——王子周棋洛   Tampermonkey首页   面向 Web 开发者的文档   Greasy Fork 新建用户脚本 打开【管理面板】 点击【】&#xff0c;即…

win10提示mfc100u.dll丢失的解决方法,快速解决dll问题

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“mfc100u.dll丢失”。那么&#xff0c;mfc100u.dll是什么&#xff1f;mfc100u.dll是Microsoft Visual C Redistributable文件之一&#xff0c;它包含了用于MFC (Microsoft Foundation Class…

win10 + cmake3.17 + vs2017编译osgearth2.7.0遇到的坑

坑1&#xff1a;debug模式下生成osgEarthAnnotation时 错误&#xff1a;xmemory0(881): error C2440: “初始化”: 无法从“std::pair<const _Kty,_Ty>”转换为 to _Objty 出错位置&#xff1a;src/osgEarthFeatures/FeatureSourceIndexNode.cpp 解决办法&#xff1a; …

pix2tex - LaTeX OCR 安装使用记录

系列文章目录 文章目录 系列文章目录前言一、安装二、使用三、如果觉得内容不错&#xff0c;请点赞、收藏、关注 前言 项目地址&#xff1a;这儿 一、安装 版本要求 Python: 3.7 PyTorch: >1.7.1 安装&#xff1a;pip install "pix2tex[gui]" 注意&#xff1a…

高德Go生态建设与研发实践

序 高德在构建Go生态演化过程中&#xff0c;已经实现了QPS从0到峰值千万的飞跃&#xff0c;本篇文章主要介绍在此过程中积累的一些技术决策及性能优化和重构经验。阅读本文读者会有以下3点收获&#xff1a; 1.高德Go生态发展历程及现状分析 2.高德云原生Serverless落地情况&…

第七章 Python常用函内置函数

系列文章目录 第一章 Python 基础知识 第二章 python 字符串处理 第三章 python 数据类型 第四章 python 运算符与流程控制 第五章 python 文件操作 第六章 python 函数 第七章 python 常用内建函数 第八章 python 类(面向对象编程) 第九章 python 异常处理 第十章 python 自定…

Proteus仿真--1602LCD显示电话拨号键盘按键实验(仿真文件+程序)

本文主要介绍基于51单片机的LCD1602显示电话拨号键盘按键实验&#xff08;完整仿真源文件及代码见文末链接&#xff09; 仿真图如下 其中右下方12个按键模拟仿真手机键盘&#xff0c;使用方法同手机键一样&#xff0c;拨打手机号码则在液晶显示屏上显示对应的号码 仿真运行…

Openssl生成证书-nginx使用ssl

Openssl生成证书并用nginx使用 安装openssl yum install openssl -y创库目录存放证书 mkdir /etc/nginx/cert cd /etc/nginx/cert配置本地解析 cat >>/etc/hosts << EOF 10.10.10.21 kubernetes-master.com EOF10.10.10.21 主机ip、 kubernetes-master.com 本…

【NeurIPS 2020】基于蒙特卡罗树搜索的黑箱优化学习搜索空间划分

Learning Search Space Partition for Black-box Optimization using Monte Carlo Tree Search 目标&#xff1a;从采样&#xff08;Dt ∩ ΩA&#xff09;中学习一个边界&#xff0c;从而最大化两方的差异 先使用Kmeans在特征向量上&#xff08; [x, f(x)] &#xff09;聚类…

OCS2工具箱

实时系统优化控制工具箱 参考视频&#xff1a;ETH 最优控制/MPC 实时求解器 OCS2 使用入门 参考文档&#xff1a;OCS2 求解器入门 选择OCS2 OCS2 是一个 MPC 实时求解器 (SLQ/iLQR)&#xff0c;依赖 Pinocchio 构建机器人动力学模型&#xff0c;采用 RViz 或者 RaiSim 验证 (…

快速解决mfc140u.dll丢失问题,找不到mfc140u.dll修复方法分享

在计算机使用过程中&#xff0c;我们可能会遇到各种问题&#xff0c;其中之一就是某些dll文件丢失。最近&#xff0c;我就遇到了一个关于mfc140u.dll丢失的问题。mfc140u.dll是Microsoft Foundation Class&#xff08;MFC&#xff09;库中的一个动态链接库文件&#xff0c;它包…

Qt中正确的设置窗体的背景图片的几种方式

Qt中正确的设置窗体的背景图片的几种方式 QLabel加载图片方式之一Chapter1 Qt中正确的设置窗体的背景图片的几种方式一、利用styleSheet设置窗体的背景图片 Chapter2 Qt的主窗口背景设置方法一&#xff1a;最简单的方式是通过ui界面来设置&#xff0c;例如设置背景图片方法二 &…

高匿IP有什么作用

在互联网的蓬勃发展中&#xff0c;IP地址作为网络通信的基础&#xff0c;一直扮演着举足轻重的角色。而在诸多IP地址中&#xff0c;高匿IP地址则是一种特殊类型&#xff0c;其作用和价值在某些特定场合下尤为突出。那么&#xff0c;高匿IP地址究竟有哪些用处呢&#xff1f; 首先…

[云原生1. ] Docker consul的详细介绍(容器服务的更新与发现)

文章目录 1. 服务注册与发现的概述1.1 cmp问题1.2 解决方法 2. Consul的概述2.1 简介2.2 为什么要使用Consul服务模块2.2 Consul的服务架构2.3 Consul的一些关键特性 3. consul服务部署3.1 前置准备3.2 Consul服务器3.2.1 建立 Consul 服务3.2.2 设置代理&#xff0c;在后台启动…

python爬虫(数据获取——selenium)

环境测试 from selenium import webdriverchromedriver_path r"C:\Program Files\Google\Chrome\Application\chromedriver.exe" driver webdriver.Chrome()url "https://www.xinpianchang.com/discover/article?fromnavigator" driver.get(url)drive…

使用lua-resty-request库编写爬虫IP实现数据抓取

目录 一、lua-resty-request库介绍 二、使用lua-resty-request库进行IP数据抓取 1、获取IP地址 2、设置请求 3、处理数据 三、代码实现 四、注意事项 五、总结 本文将深入探讨如何使用lua-resty-request库在爬虫程序中实现IP数据抓取。我们将首先介绍lua-resty-request…

FFmpeg直播能力更新计划与新版本发布

// 编者按&#xff1a;客户端作为直接面向用户大众的接口&#xff0c;随着技术的发展进化与时俱进&#xff0c;实现更好的服务是十分必要的。FFmpeg作为最受欢迎的视频和图像处理开源软件&#xff0c;被相关行业的大量用户青睐&#xff0c;而随着HEVC标准的发布到广泛使用&am…

SpringBoot整合Mybatis-plus

MyBatis-Plus与MyBatis区别&#xff1a; 导入坐标不同数据层实现简化 1.创建项目 2.选择依赖 3.pom文件 说明&#xff1a;配置pom.xml文件 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId>&…