青训营X豆包MarsCode技术训练营第七课|豆包MarsCode刷题

58 阅读11分钟

今天刷的也是一道难度特别难的题目:小A的移动点 小A有n个点,每个点的坐标为(xi,yi)(xi​,yi​)。她可以从一个点出发,平行于坐标轴移动,直到到达另一个点。具体来说,她可以从 (x1,y1)(x1​,y1​) 直接到达 (x2,y1)(x2​,y1​) 或者 (x1,y2)(x1​,y2​),但无法直接到达 (x2,y2)(x2​,y2​)。为了使得任意两个点之间可以互相到达,小M可以选择增加若干个新的点。

你的任务是计算最少需要增加多少个点,才能保证任意两个点之间可以通过平行于坐标轴的路径互相到达。


测试样例

样例1:

输入:n = 2, points = [[1, 1], [2, 2]]
输出:1

样例2:

输入:n = 3, points = [[1, 2], [2, 3], [4, 1]]
输出:2

样例3:

输入:n = 4, points = [[3, 4], [5, 6], [3, 6], [5, 4]]
输出:0

好的,下面我们对这道题进行详细分析。

题目理解

给定一个二维平面上的 n 个点,每个点都有一个 xy 坐标。我们可以通过在平面上添加点,使得这些点之间通过平行于坐标轴的路径连接,使得所有点都在同一个连通分量中。

题目要求我们求出为了使所有点都能连接在一起,最少需要添加多少个点。

题目本质

  • 所有的点如果可以通过平行于坐标轴的路径连接,那么实际上就是要求这些点在二维平面上的连通性。
  • 在平面上,我们可以把两个点 (x1, y1)(x2, y2) 连接起来的条件是它们共享相同的 xy 坐标,也就是说,两个点如果具有相同的横坐标 x1 == x2 或纵坐标 y1 == y2,我们就可以通过一条平行于坐标轴的路径把这两个点连接在一起。

解题思路

1. 连通性问题的转换

问题的核心是如何判断哪些点之间可以直接连通。通过平行于坐标轴的路径连接的点,实际上就是判断这些点在横坐标或纵坐标上是否相同。我们可以利用 并查集(Union-Find) 来解决这个连通性问题。

  • 我们可以先按照 x 坐标和 y 坐标对点进行合并操作。即,如果两个点有相同的横坐标 x 或纵坐标 y,那么我们就认为它们在同一个连通分量中。
  • 最终,所有点通过并查集合并之后,连通的点会属于同一个连通分量。如果一开始有多个连通分量,那么我们需要通过插入点来减少连通分量的数量。

2. 如何减少连通分量

我们知道,如果有 k 个连通分量,那么至少需要 k-1 个点才能将这些连通分量连接成一个大连通分量。因此,问题的目标是计算出当前点集的连通分量数,然后返回连通分量数减去 1,即为需要插入的最小点数。

具体实现步骤

  1. 初始化并查集

    • 初始化一个并查集,其中每个点都是一个单独的连通分量。
  2. 按横坐标合并

    • 将所有具有相同横坐标的点合并为一个连通分量。
  3. 按纵坐标合并

    • 将所有具有相同纵坐标的点合并为一个连通分量。
  4. 计算连通分量数

    • 计算最终有多少个独立的连通分量。
  5. 返回结果

    • 需要的插入点数就是连通分量数减去 1。

代码讲解

  1. 并查集实现

    • find(x):查找并返回点 x 的根节点,使用路径压缩优化。
    • union(x, y):将 xy 合并为一个连通分量。通过按秩合并优化,保证树的高度较小,提升效率。
  2. 按横纵坐标合并点

    • x_mapy_map 用于分别存储具有相同横坐标和纵坐标的点集合。遍历点集,分别将相同横坐标的点和相同纵坐标的点进行合并。
  3. 计算连通分量数量

    • 遍历所有点,利用 find() 方法找到每个点的根节点,并将不同的根节点加入 connected_components 集合中,最终集合的大小即为连通分量的数量。
  4. 返回需要插入的点数

    • 为了将所有点连接成一个连通分量,我们需要插入 连通分量数 - 1 个点。

复杂度分析

  • 时间复杂度

    • 每个 findunion 操作的时间复杂度是 ( O(\alpha(n)) ),其中 ( \alpha(n) ) 是阿克曼函数的反函数,实际值接近常数。
    • 遍历所有点进行合并的时间是 ( O(n) ),并且遍历坐标分组的操作也是 ( O(n) )。
    • 因此,整体的时间复杂度是 ( O(n \alpha(n)) ),近似为 ( O(n) )。
  • 空间复杂度

    • 我们使用了并查集结构,空间复杂度是 ( O(n) )。
    • 使用了字典存储横纵坐标分组,空间复杂度也是 ( O(n) )。

因此,整体的时间和空间复杂度都很优秀,适合解决大规模输入的情况。

示例解析

示例 1

输入:solution(2, [[1, 1], [2, 2]])

  • 两个点 (1, 1)(2, 2) 没有共享横坐标和纵坐标,因此它们是两个不同的连通分量。
  • 需要插入 1 个点将它们连接起来。

输出:1

示例 2

输入:solution(3, [[1, 2], [2, 3], [4, 1]])

  • (1, 2)(2, 3) 可以通过纵坐标连接,而 (4, 1) 和前两点没有任何相同的坐标。
  • 需要插入 2 个点来将这些点连接起来。

输出:2

示例 3

输入:solution(4, [[3, 4], [5, 6], [3, 6], [5, 4]])

  • 所有点可以通过横坐标或纵坐标连接,已经形成一个连通分量。
  • 无需插入任何点。

输出:0

总结

这道题的关键在于使用并查集处理点的连通性,通过横坐标和纵坐标对点进行合并,最终计算出最少需要插入的点数。

并查集(Union-Find),又叫不相交集合(Disjoint Set Union,DSU),是一种用于管理集合并查操作的数据结构。它可以高效地处理动态合并集合以及查询元素是否属于同一个集合的问题,特别适用于图论中的一些问题,比如判断图中两个节点是否连通,或者在动态连通性问题中非常有效。

并查集的基本思想

并查集的主要思想是将一组元素分成若干个互不重叠的集合。然后,我们支持两种操作:

  1. 查找操作(Find):用于确定某个元素属于哪个集合,或者说,查找某个元素所在集合的代表元素(根节点)。在查找过程中,如果我们遇到的元素不是根节点,我们会递归地查找其父节点,直到找到根节点。

  2. 合并操作(Union):用于将两个不同集合合并成一个集合。通常,我们通过将一个集合的根节点指向另一个集合的根节点来实现合并。

并查集的优化

并查集在基本操作上很简单,但是为了提高效率,通常会进行两种常见的优化:

1. 路径压缩(Path Compression)

路径压缩是在执行查找操作时进行的优化。查找一个元素时,我们不仅仅返回根节点,还会将路径上经过的所有节点的父节点直接指向根节点。这样做的好处是,未来访问这些节点时,不需要再次经过那么多的节点,查找的时间会变得更短。

例如,如果我们要查找某个节点时,发现它不是根节点,那么我们会递归查找它的父节点,并同时更新路径上所有经过的节点,使它们直接指向根节点。这可以大幅减少树的深度,从而加快后续的查找操作。

2. 按秩合并(Union by Rank)或按大小合并(Union by Size)

按秩合并是一种优化合并操作的方法。在合并两个集合时,通常会选择将深度较小的树合并到深度较大的树下面,这样可以保持树的深度尽可能小。树的深度越小,查找操作的效率越高。

具体而言,是树的深度,或者是树的一个估计值,通常是树的最大深度。每次合并时,我们会比较两个集合的秩,将秩较小的树合并到秩较大的树上,保证树的高度不会过高。

并查集的操作过程

查找(Find)

查找操作的目的是找到某个元素所在的集合的代表元素,或者说它的根节点。如果某个元素的父节点指向自己,那么这个元素就是它所在集合的代表元素。如果一个元素的父节点不是自己,我们需要递归地调用查找操作,直到找到根节点。

为了实现路径压缩,在查找过程中,如果我们发现当前节点的父节点不是根节点,我们就将当前节点的父节点直接指向根节点,从而加速以后的查找操作。

合并(Union)

合并操作的目的是将两个元素所在的集合合并成一个集合。如果两个元素已经在同一个集合中,那么我们不需要做任何操作。如果它们不在同一个集合中,我们就需要将它们所在的集合合并。

在合并时,我们通常采用按秩合并的策略。我们会先查找两个元素的根节点,然后将深度较小的树合并到深度较大的树下面。具体而言,合并时,根节点指向树的秩较大的根节点,从而减少树的深度。

并查集的时间复杂度

使用路径压缩和按秩合并优化后的并查集,每次操作(查找或合并)的时间复杂度接近于常数时间。严格来说,时间复杂度是 O(α(n)),其中α是阿克曼函数的反函数。阿克曼函数增长非常慢,几乎可以认为在实际应用中,时间复杂度是常数级别的。因此,采用并查集的数据结构可以在处理大量数据时,依然保持非常高效的性能。

并查集的应用

并查集在许多领域和问题中有广泛的应用,特别是在图论中。以下是几个典型应用:

  1. 图的连通性问题:在无向图中,我们可以用并查集来判断图中两个节点是否属于同一个连通分量。每当我们检查两个节点是否连通时,只需要判断它们的根节点是否相同。

  2. 动态连通性问题:动态连通性是指在某些图的应用中,我们可能会动态地增加一些边(即进行合并操作),而每次增加边后,我们需要快速判断两个节点是否仍然连通。并查集非常适合解决这类问题。

  3. 最小生成树:在 Kruskal 算法 中,使用并查集来判断添加一条边是否会形成环。每次我们考虑加入一条边时,首先使用查找操作检查该边的两个端点是否已经连通,如果已经连通,那么添加这条边就会形成环;如果未连通,则合并这两个集合。

  4. 社交网络中的连接查询:在社交网络应用中,我们可以使用并查集来处理用户之间的连接关系,例如判断两个用户是否在同一个社交圈中。

总结

并查集是一种高效的数据结构,能够快速地处理集合合并和查找操作。通过路径压缩和按秩合并的优化方法,能够在近乎常数时间内完成操作,尤其适用于处理图的连通性问题和动态连通性问题。它在许多实际问题中都具有广泛的应用,尤其是在图算法中,是一种非常重要的工具。