2026华为OD面试题004:5G网络建设

题目描述

某城市要建 5G 网络,已经选了 N 个地点放基站,编号 1 到 N。现在要在基站之间拉光纤,让所有人都能互相连通。

有些基站对之间已经铺好了光纤,有些还没有。每条候选光纤有个建设成本。求让全部基站互联互通的最小新增成本。如果无论如何都连不通,输出 -1。

N 不超过 20,候选光纤数量 M 不超过 N * (N - 1) / 2

讲个故事:老张的光纤工程队

老张是包工头,接了个 5G 基站拉光纤的活儿。

到现场一看,有些基站之间已经有光纤了,这部分不用花钱。剩下的得他自己铺,每条报价不一样。

老张心想:先把不要钱的连上,再把便宜的连上,只要所有基站都在同一个"网"里,活儿就算干完了。

这不就是最小生成树吗?

核心原理:最小生成树 + 并查集

已经铺好的光纤,相当于成本为 0 的边。问题变成:

在一个无向图中,选一些边把 N 个点连起来,总成本最小。

这就是最小生成树(MST)。

因为 N 很小,用 Kruskal 算法足够:

  1. 把所有边按成本排序
  2. 已经铺好的边成本视为 0,排在最前面
  3. 用并查集一条条加边,不形成环就加入
  4. 如果末尾所有点都在一个集合,输出总成本;否则输出 -1

怎么实现?

  1. 读入 N 和 M
  2. 读入每条边:X Y Z P,其中 P = 0 表示未建,P = 1 表示已建
  3. 已建边的成本改成 0,然后按成本排序
  4. 并查集初始化,逐条加边
  5. 统计连通块数量,判断是否能建网

代码实现

C 语言

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int u, v, cost;
} Edge;

int cmp(const void *a, const void *b) {
    return ((Edge *)a)->cost - ((Edge *)b)->cost;
}

int parent[25];

int find(int x) {
    return parent[x] == x ? x : (parent[x] = find(parent[x]));
}

int solve(int n, Edge edges[], int m) {
    qsort(edges, m, sizeof(Edge), cmp);
    for (int i = 1; i <= n; i++) parent[i] = i;

    int total = 0, edgesUsed = 0;
    for (int i = 0; i < m; i++) {
        int u = edges[i].u, v = edges[i].v;
        int pu = find(u), pv = find(v);
        if (pu != pv) {
            parent[pu] = pv;
            total += edges[i].cost;
            edgesUsed++;
        }
    }

    if (edgesUsed != n - 1) return -1;
    return total;
}

C++

#include <bits/stdc++.h>
using namespace std;

struct Edge {
    int u, v, cost;
    bool operator<(const Edge& o) const {
        return cost < o.cost;
    }
};

struct UnionFind {
    vector<int> p;
    UnionFind(int n) { p.resize(n + 1); iota(p.begin(), p.end(), 0); }
    int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
    bool unite(int x, int y) {
        x = find(x); y = find(y);
        if (x == y) return false;
        p[x] = y;
        return true;
    }
};

int solve(int n, vector<Edge> edges) {
    sort(edges.begin(), edges.end());
    UnionFind uf(n);
    int total = 0, used = 0;
    for (auto &e : edges) {
        if (uf.unite(e.u, e.v)) {
            total += e.cost;
            used++;
        }
    }
    return used == n - 1 ? total : -1;
}

Java

import java.util.*;

public class Main {
    static class Edge implements Comparable<Edge> {
        int u, v, cost;
        Edge(int u, int v, int cost) {
            this.u = u; this.v = v; this.cost = cost;
        }
        public int compareTo(Edge o) {
            return this.cost - o.cost;
        }
    }

    static class UnionFind {
        int[] p;
        UnionFind(int n) {
            p = new int[n + 1];
            for (int i = 0; i <= n; i++) p[i] = i;
        }
        int find(int x) {
            return p[x] == x ? x : (p[x] = find(p[x]));
        }
        boolean unite(int x, int y) {
            x = find(x); y = find(y);
            if (x == y) return false;
            p[x] = y;
            return true;
        }
    }

    public static int solve(int n, Edge[] edges) {
        Arrays.sort(edges);
        UnionFind uf = new UnionFind(n);
        int total = 0, used = 0;
        for (Edge e : edges) {
            if (uf.unite(e.u, e.v)) {
                total += e.cost;
                used++;
            }
        }
        return used == n - 1 ? total : -1;
    }
}

JavaScript

class UnionFind {
    constructor(n) {
        this.p = Array.from({length: n + 1}, (_, i) => i);
    }
    find(x) {
        return this.p[x] === x ? x : (this.p[x] = this.find(this.p[x]));
    }
    unite(x, y) {
        x = this.find(x); y = this.find(y);
        if (x === y) return false;
        this.p[x] = y;
        return true;
    }
}

function solve(n, edges) {
    edges.sort((a, b) => a.cost - b.cost);
    const uf = new UnionFind(n);
    let total = 0, used = 0;
    for (const {u, v, cost} of edges) {
        if (uf.unite(u, v)) {
            total += cost;
            used++;
        }
    }
    return used === n - 1 ? total : -1;
}

Python

class UnionFind:
    def __init__(self, n):
        self.p = list(range(n + 1))
    def find(self, x):
        if self.p[x] != x:
            self.p[x] = self.find(self.p[x])
        return self.p[x]
    def unite(self, x, y):
        x, y = self.find(x), self.find(y)
        if x == y:
            return False
        self.p[x] = y
        return True

def solve(n, edges):
    edges.sort(key=lambda e: e[2])
    uf = UnionFind(n)
    total = used = 0
    for u, v, cost in edges:
        if uf.unite(u, v):
            total += cost
            used += 1
    return total if used == n - 1 else -1

复杂度分析

  • 排序:O(M * log M)
  • 并查集操作:O(M * α(N)),α 是阿克曼函数反函数,约等于常数
  • 总时间复杂度:O(M * log M)
  • 空间复杂度:O(N + M)

总结一下

5G 网络建设 = 最小生成树的变体。

唯一的小 trick 是:已经铺好的光纤看成成本为 0 的边,这样 Kruskal 一视同仁,自然先选免费的,再选便宜的。

末尾判断一下用了多少条边。N 个点要连成一个树,必须刚好用 N - 1 条边。少了就说明不连通。

你平时写并查集是习惯路径压缩还是按秩合并?欢迎在评论区聊聊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

令狐掌门

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值