问题描述
小M有n个点,每个点的坐标为。她可以从一个点出发,平行于坐标轴移动,直到到达另一个点。具体来说,她可以从 直接到达 或者 ,但无法直接到达 。为了使得任意两个点之间可以互相到达,小M可以选择增加若干个新的点。
你的任务是计算最少需要增加多少个点,才能保证任意两个点之间可以通过平行于坐标轴的路径互相到达。
算法设计
这道题的本质是通过图的连通性分析,求解在二维坐标平面中最少新增点的数量,确保任意两个点都可以通过平行于坐标轴的路径互相到达。
1. 问题分析
-
给定
n个点,每个点有坐标 。 -
点与点之间的连通规则:若两点的
x坐标相同,或y坐标相同,则这两点是直接连通的。 -
需要确保整个图是一个连通图,即任何两个点之间存在路径相连。
-
如果初始点之间不连通,需要通过增加点的方式使其连通。
2. 算法设计
目标:计算最少需要增加的点数,使所有点形成一个连通图。
步骤 1:构造图的连通性
利用并查集管理点的连通性:
-
并查集作用:维护每个点属于哪个连通分量,快速合并两个点的连通关系。
-
规则:
- 如果两个点的
x坐标相同,则它们连通。 - 如果两个点的
y坐标相同,则它们连通。
- 如果两个点的
为了高效处理相同 x 和 y 坐标的点:
-
哈希表存储分组:
- :存储所有 xxx 坐标为
x的点索引。 - :存储所有 yyy 坐标为
y的点索引。
- :存储所有 xxx 坐标为
-
遍历哈希表的每个分组,将同组的点通过并查集合并。
步骤 2:统计连通分量数量
- 遍历所有点,通过并查集找到每个点的根节点。
- 根节点的数量即为当前的连通分量数量。
步骤 3:计算新增点数
- 如果图有
k个连通分量,则需要增加k-1个点,才能将所有分量连通。
3. 核心操作说明
并查集操作
-
初始化(init):
- 每个点开始时独立成一个集合。
parent[i] = i,表示每个点是自己的根。rank[i] = 0,表示集合的高度。
-
合并集合(unite) :
- 将两个点的根节点连通。
- 利用按秩合并优化,保证树的高度尽量小。
-
查找根节点(find) :
- 找到一个点所属集合的根节点。
- 利用路径压缩优化,减少后续查找的复杂度。
哈希表分组
-
将
n个点按x和y坐标分组,避免重复比较:- 遍历所有点,根据
x坐标存入 。 - 根据
y坐标存入 。
- 遍历所有点,根据
连通分量统计
- 对每个点调用
find,得到其根节点。 - 根节点的去重计数即为连通分量的数量。
4. 算法完整流程
-
输入处理:
- 读取
n个点及其坐标。 - 初始化并查集,大小为
n。
- 读取
-
构建连通性:
-
按
x和y坐标分组:- 对每个 ,将所有点通过并查集合并。
- 对每个 ,将所有点通过并查集合并。
-
-
统计连通分量:
- 遍历所有点,计算根节点的数量。
-
计算结果:
- 返回连通分量数量减 1 的值。
代码实现
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
class UnionFind {
public:
vector<int> parent, rank;
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 0);
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
};
int solution(int n, std::vector<std::vector<int>> points) {
UnionFind uf(n);
unordered_map<int, vector<int>> x_map, y_map;
for (int i = 0; i < n; i++) {
x_map[points[i][0]].push_back(i);
y_map[points[i][1]].push_back(i);
}
for (auto& entry : x_map) {
for (int i = 1; i < entry.second.size(); i++) {
uf.unite(entry.second[0], entry.second[i]);
}
}
for (auto& entry : y_map) {
for (int i = 1; i < entry.second.size(); i++) {
uf.unite(entry.second[0], entry.second[i]);
}
}
unordered_map<int, bool> component;
for (int i = 0; i < n; i++) {
component[uf.find(i)] = true;
}
return component.size() - 1;
}
int main() {
std::cout << solution(2, {{1, 1}, {2, 2}}) << std::endl;
std::cout << solution(3, {{1, 2}, {2, 3}, {4, 1}}) << std::endl;
std::cout << solution(4, {{3, 4}, {5, 6}, {3, 6}, {5, 4}}) << std::endl;
return 0;
}
复杂度分析
-
时间复杂度:
- 坐标分组(哈希表插入):。
- 并查集操作:最坏情况下 (路径压缩 + 按秩合并)。
- 遍历点集合统计连通分量:。
- 总复杂度:。
-
空间复杂度:
- 并查集存储:。
- 坐标哈希表:。
- 总空间复杂度:。
示例分析
示例 1:
输入:n = 2, points = {{1, 1}, {2, 2}}
-
分组:
- x坐标分组:
1 -> {0},2 -> {1}。 - y坐标分组:
1 -> {0},2 -> {1}。
- x坐标分组:
-
并查集初始:
parent = [0, 1]。 -
合并后连通分量:
{0, 1}。 -
最少新增点:。
示例 2:
输入:n = 3, points = {{1, 2}, {2, 3}, {4, 1}}
-
分组:
- x坐标分组:
1 -> {0},2 -> {1},4 -> {2}。 - y坐标分组:
2 -> {0},3 -> {1},1 -> {2}。
- x坐标分组:
-
并查集初始:
parent = [0, 1, 2]。 -
合并后连通分量:
{0, 1, 2}。 -
最少新增点:。
示例 3:
输入:n = 4, points = {{3, 4}, {5, 6}, {3, 6}, {5, 4}}
-
分组:
- x坐标分组:
3 -> {0, 2},5 -> {1, 3}。 - y坐标分组:
4 -> {0, 3},6 -> {1, 2}。
- x坐标分组:
-
并查集初始:
parent = [0, 1, 2, 3]。 -
合并后连通分量:
{0}。 -
最少新增点:。
总结
- 关键点:通过并查集解决连通分量问题,利用x和y坐标分组快速合并点。
- 效率:算法复杂度低,适合处理大规模数据。
- 实用性:题目抽象为图的连通性问题,具有广泛应用场景。