连接所有点的最小费用

这题本质上不是最短路径,而是最小生成树:我们关心的是把所有点连成一张网的总代价最小,而不是某两点之间的距离最短。

问题描述

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

给定一个数组points,其中points[i] = [xi, yi]表示平面上的一个点。连接两点的费用为它们之间的曼哈顿距离。返回连接所有点的最小费用。

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

说明:
连接方式: (0,0)-(2,2)-(5,2)-(7,0)-(3,10)
费用: 4 + 3 + 2 + 11 = 20

解题思路

  • 最小生成树:将点看作节点,距离看作边权
  • Prim算法:从点扩展
  • Kruskal算法:从边扩展
  • 曼哈顿距离:|x1-x2| + |y1-y2|

为什么这题不是最短路径

最短路径关心的是:

起点到终点最短

而这题关心的是:

所有点都连起来,总花费最小

这是最小生成树的标准定义,所以优先应该往 Prim / Kruskal 想。

Go 代码

Go 实现

import (
    "container/heap"
)
 
type Edge struct {
    cost, u, v int
}
 
type PQ []Edge
 
func (pq PQ) Len() int { return len(pq) }
func (pq PQ) Less(i, j int) bool { return pq[i].cost < pq[j].cost }
func (pq PQ) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] }
func (pq *PQ) Push(x interface{}) { *pq = append(*pq, x.(Edge)) }
func (pq *PQ) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    *pq = old[0 : n-1]
    return item
}
 
func minCostConnectPoints(points [][]int) int {
    n := len(points)
    if n <= 1 {
        return 0
    }
 
    manhattanDistance := func(p1, p2 []int) int {
        return abs(p1[0]-p2[0]) + abs(p1[1]-p2[1])
    }
 
    visited := make(map[int]bool)
    visited[0] = true
    totalCost := 0
 
    pq := &PQ{}
    heap.Init(pq)
 
    for i := 1; i < n; i++ {
        dist := manhattanDistance(points[0], points[i])
        heap.Push(pq, Edge{dist, 0, i})
    }
 
    for pq.Len() > 0 && len(visited) < n {
        edge := heap.Pop(pq).(Edge)
 
        if visited[edge.v] {
            continue
        }
 
        visited[edge.v] = true
        totalCost += edge.cost
 
        for i := 0; i < n; i++ {
            if !visited[i] {
                dist := manhattanDistance(points[edge.v], points[i])
                heap.Push(pq, Edge{dist, edge.v, i})
            }
        }
    }
 
    return totalCost
}
 
func abs(x int) int {
    if x < 0 {
        return -x
    }
    return x
}

易错点

这题最容易错的是把“边数很多”这件事想得太重,反而忘了最核心的 MST 结构。

  • 曼哈顿距离是边权,不是坐标差单独比较。
  • Prim 是“从已选点集向外扩展”,Kruskal 是“从小边开始选”。
  • 完全图场景下边很多,代码实现通常更偏向 Prim。
  • 不要把“连接所有点”误写成多源最短路。

复杂度分析

算法时间复杂度空间复杂度
PrimO(N² log N)O(N²)
KruskalO(N² log N)O(N²)

相关主题


返回:图算法 | 算法学习导航