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]);
        }
    };

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提高自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法LeetCode的动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法与数据结构基础》等,来深入理解这种算法思想。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值