【1971. 寻找图中是否存在路径】

news2024/11/22 21:55:54

来源:力扣(LeetCode)

描述:

有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0n - 1(包含 0n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。

请你确定是否存在从顶点 source 开始,到顶点 destination 结束的 有效路径 。

给你数组 edges 和整数 nsourcedestination,如果从 sourcedestination 存在 有效路径 ,则返回 true,否则返回 false

示例 1:

1

输入:n = 3, edges = [[0,1],[1,2],[2,0]], source = 0, destination = 2
输出:true
解释:存在由顶点 0 到顶点 2 的路径:
- 012 
- 02

示例 2:

2

输入:n = 6, edges = [[0,1],[0,2],[3,5],[5,4],[4,3]], source = 0, destination = 5
输出:false
解释:不存在由顶点 0 到顶点 5 的路径.

提示:

  • 1 <= n <= 2 * 105
  • 0 <= edges.length <= 2 * 105
  • edges[i].length == 2
  • 0 <= ui, vi <= n - 1
  • ui != vi
  • 0 <= source, destination <= n - 1
  • 不存在重复边
  • 不存在指向顶点自身的边

前言

  题目要求判断是否存在从起点 source 到终点 destination 的有效路径,等价于求图中两个顶点 source, destination 是否连通。两点连通性问题为经典问题,一般我们可以使用广度优先搜索或深度优先搜索,以及并查集来解决。

方法一:广度优先搜索

思路与算法

  使用广度优先搜索判断顶点 source 到顶点 destination 的连通性,需要我们从顶点 source 开始按照层次依次遍历每一层的顶点,检测是否可以到达顶点 destination。遍历过程我们使用队列存储最近访问过的顶点,同时记录每个顶点的访问状态,每次从队列中取出顶点 vertex 时,将其未访问过的邻接顶点入队列。

  初始时将顶点 source 设为已访问,并将其入队列。每次将队列中的节点 vertex 出队列,并将与 vertex 相邻且未访问的顶点 next 入队列,并将 next 设为已访问。当队列为空或访问到顶点 destination 时遍历结束,返回顶点 destination 的访问状态即可。

代码:

class Solution {
public:
    bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
        vector<vector<int>> adj(n);
        for (auto &&edge : edges) {
            int x = edge[0], y = edge[1];
            adj[x].emplace_back(y);
            adj[y].emplace_back(x);
        }
        vector<bool> visited(n, false);
        queue<int> qu;
        qu.emplace(source);
        visited[source] = true;
        while (!qu.empty()) {
            int vertex = qu.front();
            qu.pop();
            if (vertex == destination) {
                break;
            }
            for (int next: adj[vertex]) {
                if (!visited[next]) {
                    qu.emplace(next);
                    visited[next] = true;
                }
            }
        }
        return visited[destination];
    }
};

执行用时:632 ms, 在所有 C++ 提交中击败了15.39%的用户
内存消耗:145.6 MB, 在所有 C++ 提交中击败了38.17%的用户
复杂度分析
时间复杂度:O(n + m),其中 n 表示图中顶点的数目,m 表示图中边的数目。对于图中的每个顶点或者每条边,我们最多只需访问一次,因此时间复杂度为 O(n + m)。
空间复杂度:O(n + m),其中nn 表示图中顶点的数目,m 表示图中边的数目。空间复杂度主要取决于邻接顶点列表、记录每个顶点访问状态的数组和队列,邻接顶点列表需要的空间为 O(n + m),记录访问状态需要 O(n) 的空间,进行广度优先搜索时队列中最多只有 n 个元素,因此总的空间复杂度为 (n + m)。

方法二:深度优先搜索

思路与算法

  我们使用深度优先搜索检测顶点 source, destination 的连通性,需要从顶点 source 开始依次遍历每一条可能的路径,判断可以到达顶点 destination,同时还需要记录每个顶点的访问状态防止重复访问。

  首先从顶点 source 开始遍历并进行递归搜索。搜索时每次访问一个顶点 \textit{vertex} vertex 时,如果 vertex 等于 destination 则直接返回,否则将该顶点设为已访问,并递归访问与 vertex 相邻且未访问的顶点 next。如果通过 next 的路径可以访问到 destination,此时直接返回 true,当访问完所有的邻接节点仍然没有访问到 destination,此时返回 false。

代码:

class Solution {
public:
    bool dfs(int source, int destination, vector<vector<int>> &adj, vector<bool> &visited) {
        if (source == destination) {
            return true;
        }
        visited[source] = true;
        for (int next : adj[source]) {
            if (!visited[next] && dfs(next, destination, adj, visited)) {
                return true;
            }
        }
        return false;
    }

    bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
        vector<vector<int>> adj(n);
        for (auto &edge : edges) {
            int x = edge[0], y = edge[1];
            adj[x].emplace_back(y);
            adj[y].emplace_back(x);
        }
        vector<bool> visited(n, false);
        return dfs(source, destination, adj, visited);
    }
};

执行用时:496 ms, 在所有 C++ 提交中击败了33.71%的用户
内存消耗:207.4 MB, 在所有 C++ 提交中击败了11.33%的用户
复杂度分析
时间复杂度:O(n + m),其中 n 表示图中顶点的数目,m 表示图中边的数目。对于图中的每个顶点或者每条边,我们最多只需访问一次,对于每个顶因此时间复杂度为 O(n + m)。
空间复杂度:O(n + m),其中 n 表示图中顶点的数目,m 表示图中边的数目。空间复杂度主要取决于邻接顶点列表、记录每个顶点访问状态的数组和递归调用栈,邻接顶点列表需要 O(m + n)的存储空间,记录每个顶点访问状态的数组和递归调用栈分别需要 O(n)O(n) 的空间,因此总的空间复杂度为 O(m + n)。

方法三:并查集

思路与算法

  我们将图中的每个强连通分量视为一个集合,强连通分量中任意两点均可达,如果两个点 source 和 destination 处在同一个强连通分量中,则两点一定可连通,因此连通性问题可以使用并查集解决。

  并查集初始化时,n 个顶点分别属于 n 个不同的集合,每个集合只包含一个顶点。初始化之后遍历每条边,由于图中的每条边均为双向边,因此将同一条边连接的两个顶点所在的集合做合并。

  遍历所有的边之后,判断顶点 source 和顶点 destination 是否在同一个集合中,如果两个顶点在同一个集合则两个顶点连通,如果两个顶点所在的集合不同则两个顶点不连通。

代码:

class UnionFind {
public:
    UnionFind(int n) {
        parent = vector<int>(n);
        rank = vector<int>(n);
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }

    void uni(int x, int y) {
        int rootx = find(x);
        int rooty = find(y);
        if (rootx != rooty) {
            if (rank[rootx] > rank[rooty]) {
                parent[rooty] = rootx;
            } else if (rank[rootx] < rank[rooty]) {
                parent[rootx] = rooty;
            } else {
                parent[rooty] = rootx;
                rank[rootx]++;
            }
        }
    }

    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    bool connect(int x, int y) {
        return find(x) == find(y);
    }
private:
    vector<int> parent;
    vector<int> rank;
};

class Solution {
public:
    bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
        if (source == destination) {
            return true;
        }
        UnionFind uf(n);
        for (auto edge : edges) {
            uf.uni(edge[0], edge[1]);
        }
        return uf.connect(source, destination);
    }
};

执行用时:360 ms, 在所有 C++ 提交中击败了55.85%的用户
内存消耗:124.8 MB, 在所有 C++ 提交中击败了43.39%的用户
复杂度分析
时间复杂度: O(n + m × α(m)),其中 n 是图中的顶点数,m 是图中边的数目,α 是反阿克曼函数。并查集的初始化需要 O(n) 的时间,然后遍历 m 条边并执行 m 次合并操作,最后对 source 和 destination 执行一次查询操作,查询与合并的单次操作时间复杂度是 O(α(m)),因此合并与查询的时间复杂度是 O(m×α(m)),总时间复杂度是O(n+m×α(m))。
空间复杂度:O(n),其中 n 是图中的顶点数。并查集需要 O(n) 的空间。
author:LeetCode-Solution

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

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

相关文章

计算机毕设Python+Vue学衡国学堂围棋社管理系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

nvidia 使用

watch -n 0.5 nvidia-smi ./build/examples/openpose/openpose.bin --video examples/media/video.avi Linux CPU&GPU烤机&#xff08;压力测试&#xff09; 盛夏捷关注IP属地: 青海 0.1342021.04.14 09:50:16字数 152阅读 6,307 GPU-burn工具进行GPU烤机 下载Multi-G…

基于MATLAB的车牌识别系统设计(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

vue之非父子组件通信实现方式

在开发中&#xff0c;我们构建了组件树之后&#xff0c;除了父子组件之间的通信之外&#xff0c;还会有非父子组件之间的通信。这里主要讲两种方式&#xff1a; Provide/InjectMitt全局事件总线 1、Provide和Inject 应用场景 比如有一些深度嵌套的组件&#xff0c;子组件想要…

SVG 在前端的7种使用方法,你还知道哪几种?

本文简介 点赞 关注 收藏 学会了 技术一直在演变&#xff0c;在网页中使用 SVG 的方法也层出不穷。每个时期都有对应的最优解。 所以我打算把我知道的 7种 SVG 的使用方法列举出来&#xff0c;有备无患~ 如果你还知道其他方法&#xff0c;可以在评论区补充~ 1. 在浏览器直…

PMO(项目管理办公室)的未来趋势

PMO&#xff08;项目管理办公室&#xff09;是在组织内部将实践、过程、运作形式化和标准化的部门&#xff0c;也是提高组织管理成熟度的核心部门。现在&#xff0c;让我们把目光投向当前PMO的典型职责之外&#xff0c;思考一下&#xff1a;PMO的未来是什么&#xff1f; 如今&a…

Java:进一步理解多态性

Java&#xff1a;进一步理解多态性 每博一文案 有人说我心里有事&#xff0c;但我谁也不想说&#xff0c;沉默不是没有情绪&#xff0c;而是我明白了&#xff0c;说了又没有意义&#xff0c; 比起诉说的委屈和不甘&#xff0c;沉默或许更好。当我们经历越多真实与虚假&#xf…

Docker安装(centos 7)

安装 以在centos安装为例&#xff0c;主要有以下几个步骤 1、确定你是CentOS7及以上版本 2、卸载旧版本 3、yum安装gcc相关 yum -y install gccyum -y install gcc-c 4、安装需要的软件包 执行如下命令 yum -y install gcc-c 5、设置stable镜像仓库 由于docker外网镜像…

Docker安装canal-admin以及canal-server

一、安装canal-admin可视化管理工具 此处的数据库已经进行了相应的配置&#xff0c;望周知 docker run -it --name canal-admin \ -e spring.datasource.addressxxx:3306 \ -e spring.datasource.databasecanal_manager \ -e spring.datasource.usernameroot \ -e spring.da…

minicom发送AT指令

参考&#xff1a;使用minicom发AT指令&#xff0c;和外设传感器通信 地址&#xff1a;https://blog.csdn.net/hannibaychty/article/details/125463268 目录1、Linux minicom 和 windows串口调试助手的区别2、使用的基本流程3、使用 minicom 需要注意的几点ARM板子外接传感器&a…

从国企到进大厂,全靠阿里、腾讯内网22版Java核心手册合集

记得19年初的时候&#xff0c;我通过一整天的笔试及面试加入一家(某一线城市国资委全资控股)某集团的研究机构(中央研究院)&#xff0c;任职高级软件工程师(中级职称);在这边工作了整整一年&#xff0c;目前已经跳槽到一家互联网公司&#xff0c;在回头看看这一整年&#xff0c…

SAP如何删除一个已经释放的请求 (SE38 : RDDIT076)

原文链接&#xff1a;https://zhuanlan.zhihu.com/p/425479956 当你搜到这个文章的时候&#xff0c;说明你做了羞羞的事情哦&#xff5e;&#xff08;或者正在准备做羞羞的事情&#xff09;。 此处声明&#xff1a;本帖仅进行可操作性和纯技术讨论&#xff0c;由此造成的一切后…

MMSegmentation使用记录

一、官网下载文件&#xff1a; 当前最新版本为1.0.0rc2 https://github.com/open-mmlab/mmsegmentation/releases/tag/v1.0.0rc2 下载源码解压文件可得到最新版的代码 二、配置环境&#xff1a; 这部分省略&#xff1a;按照给的README文件很快就配置好了 https://github.…

Vuetify中的v-pagination如何实现分页

大家好&#xff0c;我是雄雄。 前言 昨天在改一个系统的时候遇到了个技能点&#xff0c;观察解决了好久&#xff0c;终于解决了&#xff0c;趁热打铁&#xff0c;今天来记录一下。 这个系统是个个人博客&#xff0c;目前我也在使用&#xff0c;但是有个地方用的很不舒服。就是…

32 CPP多态

注意: 1 只需要在基类的函数声明中加上virtual关键字&#xff0c;函数定义时不能加&#xff1b; 2 在派生类中重定义虚函数时&#xff0c;函数特征要相同&#xff1b; 3 当在基类中定义了虚函数时&#xff0c;如果派生类没有重定义该函数&#xff0c;那么将使用基类的虚函数…

设计模式原则 - 接口隔离原则(二)

接口隔离原则一 官方定义二 案例演示普通方案案例分析解决方案解决方案案例总结三 与单一职责原则对比一 官方定义 接口隔离原则&#xff08;Interface Segregation Principle&#xff09;&#xff0c;又称为ISP原则&#xff0c;官方定义为&#xff1a; Clients should not be…

docker安装seata单节点的详细教程

一、环境部署 1、在自己的数据库新建seata数据库 2、利用seata官方提供的seata数据库sql脚本创建所需数据库seata以及表&#xff0c;脚本地址如下&#xff1a; seata/mysql.sql at 1.4.1 seata/seata GitHub 3、查看docker官方镜像仓库版本 4、拉取seata安装镜像 docker…

【机器学习实战】基于代价敏感学习的AdaCost方法用于信用卡欺诈检测

1. 数据集 数据集地址&#xff1a;Credit Card Fraud Detection 数据集整体浏览&#xff1a; 284807个样本&#xff0c;30个特征&#xff0c;1个分类标签Class Class为0的是多数类&#xff0c;一共有284315个样本。 Class为1的是少数类&#xff0c;一共有492个样本&#xff…

写给Python社群的第11课:Python线程,进程,协程,3个毫无关系的兄弟

文章目录⛳️ 线程、进程与协程&#x1f525; 进程与线程简介⛳️ Python 多线程模块&#x1f525; threading 模块&#x1f525; threading 模块实践⛳️ Python 并发进程模块&#x1f525; Process 创建多进程⛳️ 线程、进程与协程 线程、进程、协程 这三个名称相似的概念&…

大话设计模型 Task03:工厂、制造、观察

目录一、建造者模式问题描述问题分析模式定义代码实现二、观察者模式问题描述问题分析模式定义代码实现一、建造者模式 问题描述 我的要求是你用程序画一个小人&#xff0c;这在游戏程序里非常常见&#xff0c;现在简单一点&#xff0c;要求是小人要有头、身体、两手、两脚就可…