LeetCode - 1584. 连接所有点的最小费用

描述

给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。

连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。

请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。

示例 1:

输入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
输出:20
解释:

我们可以按照上图所示连接所有点得到最小总费用,总费用为 20 。
注意到任意两个点之间只有唯一一条路径互相到达。
示例 2:

输入:points = [[3,12],[-2,5],[-4,1]]
输出:18
示例 3:

输入:points = [[0,0],[1,1],[1,0],[-1,1]]
输出:4
示例 4:

输入:points = [[-1000000,-1000000],[1000000,1000000]]
输出:4000000
示例 5:

输入:points = [[0,0]]
输出:0
 

提示:

1 <= points.length <= 1000
-106 <= xi, yi <= 106
所有点 (xi, yi) 两两不同。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/min-cost-to-connect-all-points/

求解

    class UnionFind {
    public:
        UnionFind(int n) : count(n) {
            parent.reserve(count + 1);
            for (int i = 0; i <= count; ++i) {
                parent[i] = i;
            }
            rank.resize(count + 1, 1);  // 初始每个的层级均为1
        }

        bool isConnected(int p, int q) {
            return find(p) == find(q);
        }

        void unionElements(int p, int q) {
            int proot = find(p);
            int qroot = find(q);
            if (proot == qroot) {
                return;
            }

            if (rank[proot] < rank[qroot]) {
                parent[proot] = qroot;
            } else if (rank[proot] > rank[qroot]) {
                parent[qroot] = proot;
            } else {
                // rank[proot] == rank[qroot]
                parent[proot] = qroot;
                ++rank[qroot];  // proot ”挂载“到qroot下面,本来两个层级一致,现在需要增加1
            }
        }

    private:
        int find(int p) {
            while (p != parent[p]) {
                parent[p] = parent[parent[p]]; // 路径压缩优化,请细品
                p = parent[p];
            }
            return p;
        }

    private:
        std::vector<int> parent;
        int count;
        std::vector<int> rank;
    };

    struct Edge {
        Edge() {}

        Edge(int a, int b, int w) : v(a), w(b), weight(w) {};
        int v = 0; // 顶点v
        int w = 0; // 顶点w
        int weight = 0; // 边v-w的权值
    };

    class Solution {
    public:
        // 方法一,构造图,prim算法计算最小生成树
        int minCostConnectPoints_1e(vector<vector<int>> &points) {
            const int n = points.size();
            if (n <= 1) {
                return 0;
            }
            vector<vector<int>> graph(n, vector<int>(n, INTMAX)); // 邻接矩阵存储图
            // 构造图
            for (int i = 0; i < n; ++i) {
                for (int j = i + 1; j < n; ++j) {
                    graph[i][j] = graph[j][i] = distance(points[i], points[j]);
                }
            }

            // 求最小生成树
            vector<bool> visited(n, false); // 标记节点是否被访问,即是否加入最小生成树中
            int weight = 0; // 最小生成树权值
            visited[0] = true;  // 首先将节点0添加进去
            vector<int> lowcost = graph[0];
            for (int i = 1; i < n; ++i) {
                int index;
                int minEdge = INTMAX;
                // 寻找还未加入最小生成树中节点到最小生成树中节点权值最小边
                for (int k = 0; k < n; ++k) {
                    if (visited[k]) {
                        continue;
                    }
                    if (lowcost[k] < minEdge) {
                        minEdge = lowcost[k];
                        index = k;
                    }
                }
                weight += minEdge;
                visited[index] = true; // 将当前最小权值得边加入最小生成树中

                // 更新最小边
                for (int k = 0; k < n; ++k) {
                    if (visited[k]) {
                        continue;
                    }
                    if (graph[index][k] < lowcost[k]) {
                        lowcost[k] = graph[index][k];
                    }
                }
            }

            // 返回最小生成树的权值
            return weight;
        }

        // 方法二,构造图,kruskal算法计算最小生成树, 效率较低
        int minCostConnectPoints(vector<vector<int>> &points) {
            const int n = points.size();
            if (n <= 1) {
                return 0;
            }
            // 存储所有边的权值,无向图对称性,只需要存储一半即可
            vector<Edge> edges;
            edges.reserve(n * (n - 1) / 2);
            // 计算边权值
            for (int i = 0; i < n; ++i) {
                for (int j = i + 1; j < n; ++j) {
                    edges.emplace_back(i, j, distance(points[i], points[j]));
                }
            }

            //
            std::sort(edges.begin(), edges.end(),
                      [](const auto &lhs, const auto &rhs) noexcept { return lhs.weight < rhs.weight; });
            UnionFind uf(n);
            int res = 0;
            int edgeNum = 0;
            for (auto &edge : edges) {
                if (uf.isConnected(edge.v, edge.w)) {
                    continue;
                }
                res += edge.weight;
                uf.unionElements(edge.v, edge.w);
                ++edgeNum;
                if (edgeNum == n - 1) {
                    // 已选取n-1条边构成最小生成树后,终止寻找过程
                    return res;
                }
            }

            return res;
        }

    private:
        const int INTMAX = std::numeric_limits<int>::max();

        inline int distance(const vector<int> &point1, const vector<int> &point2) const {
            return abs(point1[0] - point2[0]) + abs(point1[1] - point2[1]);
        }
    };

猜你喜欢

转载自blog.csdn.net/u010323563/article/details/112852125