leetcode
leetcode 1001 ~ 1050
水资源分配优化

水资源分配优化

难度:

标签:

题目描述

代码结果

运行时间: 93 ms, 内存: 22.3 MB


// Leetcode 1168: Optimize Water Distribution in a Village
// Problem: Given a number of houses and a number of wells, we need to find the minimum cost to supply water to all houses.
// Solution: This problem can be solved using Kruskal's algorithm for Minimum Spanning Tree (MST). We treat each house and well as nodes in a graph, 
// and the pipes as edges with weights corresponding to the costs. We then find the MST of this graph to get the minimum cost.

import java.util.*;
import java.util.stream.*;

class Solution {
    public int minCostToSupplyWater(int n, int[] wells, int[][] pipes) {
        // Create a list to hold all the edges (pipes and wells)
        List<int[]> edges = IntStream.range(0, wells.length)
                                    .mapToObj(i -> new int[]{0, i + 1, wells[i]})
                                    .collect(Collectors.toList());

        // Add the given pipes as edges
        edges.addAll(Arrays.asList(pipes));

        // Sort edges by cost
        edges.sort(Comparator.comparingInt(a -> a[2]));

        // Create a union-find (disjoint-set) structure
        int[] parent = IntStream.range(0, n + 1).toArray();

        // Function to find the root of a node
        int find(int x) {
            if (parent[x] != x) {
                parent[x] = find(parent[x]);
            }
            return parent[x];
        }

        // Function to union two sets
        void union(int x, int y) {
            parent[find(x)] = find(y);
        }

        final int[] totalCost = {0};

        // Kruskal's algorithm to find MST
        edges.stream()
              .filter(edge -> find(edge[0]) != find(edge[1]))
              .forEach(edge -> {
                  union(edge[0], edge[1]);
                  totalCost[0] += edge[2];
              });

        return totalCost[0];
    }
}

解释

方法:

该题解采用了并查集数据结构来解决最小生成树问题,具体为Kruskal算法的变体。首先,将每个水井视为与每个房子直接相连的特殊节点,将其连接成本作为边的权重。将所有的管道和水井到房子的连接都看作边,并按照成本对这些边进行排序。接下来,使用并查集来逐一检查排序后的边,如果连接的两个节点不在同一集合中,则将这两个节点合并,并将边的成本加到总成本中。通过并查集操作,保证图中不会形成环,最终当所有房子都被连接到水源后(即并查集的根节点包括所有节点),算法结束。

时间复杂度:

O((m+n)log(m+n))

空间复杂度:

O(m+n)

代码细节讲解

🦆
在Kruskal算法中,为什么首先需要将所有边按成本进行排序?这一步骤对算法的正确性和效率有何影响?
Kruskal算法是一种贪心算法,其目标是找到图中连接所有节点的最小生成树。通过首先将所有边按成本进行排序,算法可以从最小的边开始选择,确保每次添加的边都是当前可用边中成本最低的,这有助于保证最终生成树的总成本是最低的。这一策略符合贪心算法的选择性标准,确保每步选择都是局部最优的,从而达到全局最优。排序步骤还提高了算法的效率,因为一旦遍历到的边导致所有节点都已连通,算法就可以提前终止,避免了无效的检查。
🦆
并查集中的路径压缩技术具体是如何实现的?它如何影响并查集操作的效率?
并查集的路径压缩技术是在执行查找操作时实现的。当调用查找函数以确定某个节点的根节点时,路径压缩技术会确保节点到根节点路径上的所有节点都直接连接到根节点。这通过递归地将节点的父节点设置为其根节点来完成。路径压缩显著提高了并查集的效率,因为它减少了未来操作中路径的长度,从而加快了查找和合并操作的执行速度。长期看,这使得并查集的操作几乎可以在常数时间内完成,极大地提高了数据结构的性能。
🦆
题解中提到的‘阿克曼函数的反函数α(n)’在并查集的上下文中具体是如何工作的?为什么在实际应用中可以视为常数?
在并查集的上下文中,阿克曼函数的反函数α(n)用于描述查找操作的均摊时间复杂度。由于阿克曼函数增长极慢,其反函数α(n)即使在非常大的n值下也增长非常缓慢,通常不会超过4或5。因此,在实际应用中,即使n的值非常大,α(n)的值仍然非常小,可以近似为常数。这意味着并查集操作的时间复杂度可以被视为几乎是常数时间,这对于算法的效率具有极大的影响。

相关问题