Leetcode 最小生成树系列(2)

发布于:2025-08-16 ⋅ 阅读:(20) ⋅ 点赞:(0)

水资源分配优化

村里面一共有 nnn 栋房子。我们希望通过建造水井和铺设管道来为所有房子供水。

对于每个房子 iii,我们有两种可选的供水方案:一种是直接在房子内建造水井,成本为 wells[i−1]wells[i - 1]wells[i1] (注意 −1-11,因为 索引从000开始 );另一种是从另一口井铺设管道引水,数组 pipespipespipes 给出了在房子间铺设管道的成本,其中每个 pipes[j]=[house1j,house2j,costj]pipes[j] = [house1_{j}, house2_{j}, cost_{j}]pipes[j]=[house1j,house2j,costj] 代表用管道将 house1jhouse1_{j}house1jhouse2jhouse2_{j}house2j连接在一起的成本。连接是双向的。

请返回为所有房子都供水的最低总成本 。

示例:
在这里插入图片描述
输入: n = 3, wells = [1,2,2], pipes = [[1,2,1],[2,3,1]]
输出: 3
解释:
上图展示了铺设管道连接房屋的成本。最好的策略是在第一个房子里建造水井(成本为 1),然后将其他房子铺设管道连起来(成本为 2),所以总成本为 3。

提示:

2<=n<=1042 <= n <= 1042<=n<=104
wells.length==nwells.length == nwells.length==n
0<=wells[i]<=1050 <= wells[i] <= 1050<=wells[i]<=105
1<=pipes.length<=1041 <= pipes.length <= 1041<=pipes.length<=104
pipes[j].length==3pipes[j].length == 3pipes[j].length==3
1<=house1j,house2j<=n1 <= house1j, house2j <= n1<=house1j,house2j<=n
0<=costj<=1050 <= costj <= 1050<=costj<=105
house1j!=house2jhouse1_{j} != house2_{j}house1j!=house2j

题解:

题目的核心技巧是引入虚拟节点,把“打井”也看作是建一条特殊的边:增加一个虚拟节点 0,表示“水源”。对每个房子 𝑖𝑖i,加一条边 (0,i,wells[i−1])(0, i, wells[i-1])(0,i,wells[i1]),代表直接在该房子打井。原有 pipespipespipes 数据就是房子之间的无向边。问题转化为:在包含 0,1,2,…,n0, 1, 2, …, n0,1,2,,n 节点的图上,求一棵最小生成树(MST)。

MST 会自动帮我们决定:哪些房子直接打井(连接到 0 节点的边被选中)。哪些房子通过管道连接。

算法求解步骤

  • 把所有虚拟边 (0, i, wells[i-1]) 加入。
  • 把所有管道边 (house1, house2, cost) 加入。
  • 排序
  • 将所有边按 cost 从小到大排序。
  • 并查集合并
  • 初始化并查集,节点范围 [0, n]。
  • 从最小成本边开始遍历,若两个端点不在同一集合,就合并并加到总成本中。
  • 当选中的边数量达到 n(因为有 n+1 个节点)时结束。
  • 输出结果
  • 累加的边权和即为最小成本。

算法代码实现

class Solution {

    static class DSU {
        int[] parent;
        int[] size;

        DSU(int n) {
            parent = new int[n];
            size = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                size[i] = 1;
            }
        }

        int find(int x) {
            while (x != parent[x]) {
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }

        boolean union(int a, int b) {
            int ra = find(a), rb = find(b);
            if (ra == rb) return false;
            if (size[ra] < size[rb]) {
                int t = ra; ra = rb; rb = t;
            }
            parent[rb] = ra;
            size[ra] += size[rb];
            return true;
        }
    }

    static class Edge {
        int u, v, w;
        Edge(int u, int v, int w) { this.u = u; this.v = v; this.w = w; }
    }

    /**
     * @param n 房子数量(1..n)
     * @param wells wells[i] 为在房子 i+1 打井的成本
     * @param pipes pipes[j] = [house1, house2, cost] 为铺设管道成本(双向)
     * @return 供水的最小总成本
     */
    public static int minCostToSupplyWater(int n, int[] wells, int[][] pipes) {
        List<Edge> edges = new ArrayList<>(n + (pipes == null ? 0 : pipes.length));

        // 虚拟边:(0, i, wells[i-1]),表示在房子 i 打井
        for (int i = 1; i <= n; i++) {
            edges.add(new Edge(0, i, wells[i - 1]));
        }
        // 管道边
        if (pipes != null) {
            for (int[] p : pipes) {
                edges.add(new Edge(p[0], p[1], p[2]));
            }
        }

        // 按成本升序排序
        edges.sort(Comparator.comparingInt(e -> e.w));

        DSU dsu = new DSU(n + 1); // 节点 0..n
        long total = 0L;
        int used = 0; // 选边计数,目标为 n(n+1 个节点的生成树)

        for (Edge e : edges) {
            if (dsu.union(e.u, e.v)) {
                total += e.w;
                used++;
                if (used == n) break;
            }
        }
        return (int) total;
    }
}

运行效果

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到