小A的移动点 | 豆包MarsCode AI刷题

122 阅读5分钟

问题描述

小M有n个点,每个点的坐标为(xi,yi)(x_i,y_i)。她可以从一个点出发,平行于坐标轴移动,直到到达另一个点。具体来说,她可以从(x1,y1)(x_1,y_1) 直接到达 (x2,y1)(x_2,y_1) 或者 (x1,y2)(x_1,y_2),但无法直接到达 (x2,y2)(x_2,y_2)。为了使得任意两个点之间可以互相到达,小M可以选择增加若干个新的点。

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


算法设计

这道题的本质是通过图的连通性分析,求解在二维坐标平面中最少新增点的数量,确保任意两个点都可以通过平行于坐标轴的路径互相到达。


1. 问题分析

  • 给定 n 个点,每个点有坐标 (xi,yi)(x_i, y_i)

  • 点与点之间的连通规则:若两点的 x 坐标相同,或 y 坐标相同,则这两点是直接连通的。

  • 需要确保整个图是一个连通图,即任何两个点之间存在路径相连。

  • 如果初始点之间不连通,需要通过增加点的方式使其连通。


2. 算法设计

目标:计算最少需要增加的点数,使所有点形成一个连通图。

步骤 1:构造图的连通性

利用并查集管理点的连通性:

  • 并查集作用:维护每个点属于哪个连通分量,快速合并两个点的连通关系。

  • 规则

    1. 如果两个点的 x 坐标相同,则它们连通。
    2. 如果两个点的 y 坐标相同,则它们连通。

为了高效处理相同 xy 坐标的点:

  • 哈希表存储分组

    • x_map[x]x\_map[x]:存储所有 xxx 坐标为 x 的点索引。
    • y_map[y]y\_map[y]:存储所有 yyy 坐标为 y 的点索引。
  • 遍历哈希表的每个分组,将同组的点通过并查集合并。

步骤 2:统计连通分量数量
  • 遍历所有点,通过并查集找到每个点的根节点。
  • 根节点的数量即为当前的连通分量数量。
步骤 3:计算新增点数
  • 如果图有 k 个连通分量,则需要增加 k-1 个点,才能将所有分量连通。

3. 核心操作说明

并查集操作
  1. 初始化(init)

    • 每个点开始时独立成一个集合。
    • parent[i] = i,表示每个点是自己的根。
    • rank[i] = 0,表示集合的高度。
  2. 合并集合(unite)

    • 将两个点的根节点连通。
    • 利用按秩合并优化,保证树的高度尽量小。
  3. 查找根节点(find)

    • 找到一个点所属集合的根节点。
    • 利用路径压缩优化,减少后续查找的复杂度。
哈希表分组
  • n 个点按 xy 坐标分组,避免重复比较:

    • 遍历所有点,根据 x 坐标存入 x_mapx\_map
    • 根据 y 坐标存入 y_mapy\_map
连通分量统计
  • 对每个点调用 find,得到其根节点。
  • 根节点的去重计数即为连通分量的数量。

4. 算法完整流程

  1. 输入处理

    • 读取 n 个点及其坐标。
    • 初始化并查集,大小为 n
  2. 构建连通性

    • xy 坐标分组:

      • 对每个 x_map[x]x\_map[x],将所有点通过并查集合并。
      • 对每个 y_map[y]y\_map[y],将所有点通过并查集合并。
  3. 统计连通分量

    • 遍历所有点,计算根节点的数量。
  4. 计算结果

    • 返回连通分量数量减 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. 时间复杂度

    • 坐标分组(哈希表插入):O(n)O(n)
    • 并查集操作:最坏情况下 O(α(n))O(\alpha(n)) (路径压缩 + 按秩合并)。
    • 遍历点集合统计连通分量:O(n)O(n)
    • 总复杂度O(n)O(n)
  2. 空间复杂度

    • 并查集存储:O(n)O(n)
    • 坐标哈希表:O(n)O(n)
    • 总空间复杂度O(n)O(n)

示例分析

示例 1:

输入:n = 2, points = {{1, 1}, {2, 2}}

  • 分组:

    • x坐标分组:1 -> {0}, 2 -> {1}
    • y坐标分组:1 -> {0}, 2 -> {1}
  • 并查集初始:parent = [0, 1]

  • 合并后连通分量:{0, 1}

  • 最少新增点:21=12 - 1 = 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}
  • 并查集初始:parent = [0, 1, 2]

  • 合并后连通分量:{0, 1, 2}

  • 最少新增点:31=23 - 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}
  • 并查集初始:parent = [0, 1, 2, 3]

  • 合并后连通分量:{0}

  • 最少新增点:11=01 - 1 = 0


总结

  1. 关键点:通过并查集解决连通分量问题,利用x和y坐标分组快速合并点。
  2. 效率:算法复杂度低,适合处理大规模数据。
  3. 实用性:题目抽象为图的连通性问题,具有广泛应用场景。