2025-12-22:最小化連通分量的最大成本。用go語言,給出一個連通的無向圖,節點編號為 0 到 n-1,邊集用數組 edges 表示,其中每條邊 edges[i] = [u, v, w] 連接 u 和 v,權重為 w。允許刪除任意若干條邊,使得剩下的圖被劃分成最多 k 個連通塊(connected components)。

對每個連通塊,把其中所有邊的最大權值視為該塊的代價;若某個連通塊沒有任何邊,則其代價為 0。目標是通過刪除邊來最小化所有連通塊代價的最大值,並返回這個最小可能的最大代價。
換句話説,就是把頂點拆成不超過 k 個組(通過刪邊實現),每組的代價為組內邊權的最大值,要求使得所有組中代價的最大值儘可能小。

1 <= n <= 50000。

0 <= edges.length <= 100000。

edges[i].length == 3。

0 <= ui, vi < n。

1 <= wi <= 1000000。

1 <= k <= n。

輸入圖是連通圖。

輸入: n = 5, edges = [[0,1,4],[1,2,3],[1,3,2],[3,4,6]], k = 2。

輸出: 4。

解釋:

2025-12-22:最小化連通分量的最大成本。用go語言,給出一個連通的無向圖,節點編號為 0 到 n-1,邊集用數組 edges 表示,其中每條邊 edges[i] = [u, v, w] 連接_權重

移除節點 3 和節點 4 之間的邊(權值為 6)。

最終的連通分量成本分別為 0 和 4,因此最大代價為 4。

題目來自力扣3613。

分步過程説明

  1. 問題理解與目標確認
  • 初始狀態是一個連通的無向圖,目標是通過刪除一些邊,將圖劃分成最多 k 個連通塊
  • 每個連通塊的代價定義為該塊內所有邊中最大的權重。如果一個連通塊內沒有邊(即只有孤立節點),其代價為0。
  • 最終目標是讓所有連通塊代價中的最大值儘可能小
  1. 核心思路:逆向思考與Kruskal算法
  • 這個問題可以逆向思考:我們不是主動去“刪除”邊,而是像構建最小生成樹(MST)一樣,從空圖開始,逐步添加邊,將節點連接起來。
  • 使用 Kruskal算法 的思想。Kruskal算法通過每次選擇當前最小的邊來構建MST,如果加入該邊不會形成環,則加入。這個過程天然地保證了在連通性變化的關鍵時刻,所使用的邊權是當前最小的。
  • 我們的目標是得到最多 k 個連通塊。在Kruskal算法中,初始時有 n 個連通塊(每個節點獨立)。每成功加入一條邊,就會合並兩個連通塊,使總連通塊數減少1。
  • 因此,當連通塊數量恰好減少到 k 個時,我們停止添加邊。此時,為了將這 k 個連通塊最終連接成一棵樹(即一個連通塊),還需要添加的邊中的最大權重,就是當前我們最後加入的那條邊的權重。這個權重就是我們要找的“最小可能的最大代價”。
  1. 算法詳細步驟
  • 步驟一:排序邊
    首先,將所有的邊按照權重(w)從小到大進行排序。這是Kruskal算法的標準第一步,目的是確保我們總是先嚐試權重最小的邊。
  • 步驟二:初始化並查集
    初始化一個並查集(Union-Find)數據結構。初始時,每個節點都是一個獨立的連通塊,因此連通塊的數量 cc 等於節點數 n
  • 步驟三:按順序處理邊併合並
    從小到大遍歷排序後的邊列表:
  • 對於每條邊 [u, v, w],使用並查集檢查節點 uv 當前是否屬於同一個連通塊。
  • 如果不屬於同一個連通塊,則合併它們。這個合併操作會將兩個連通塊合併為一個,使總的連通塊數量 cc 減一。
  • 在每次成功合併後,立即檢查當前的連通塊數量 cc 是否等於目標 k
  • 步驟四:判斷終止條件並返回結果
  • 一旦在合併某條邊後,連通塊數量 cc 等於 k,算法立即停止。
  • 此時,當前這條邊的權重 w 就是答案。這是因為這條邊是我們在達到 k 個連通塊之前,為了減少連通塊數量而加入的最後一條邊。它的權重代表了為了形成這 k 個連通塊,我們所必須接受的連通塊內部的最大邊權
  • 如果遍歷完所有邊後連通塊數量仍大於 k(根據題目輸入的連通性,這種情況不會發生),則拋出異常。

複雜度分析

總的額外空間複雜度

  • O(n + m),其中 n 是節點數,m 是邊數。
  • 並查集數據結構:需要存儲每個節點的父節點信息,空間為 O(n)。
  • 邊的存儲:對輸入的邊進行排序,需要 O(m) 的空間。
  • 因此,總的空間複雜度主要由並查集和邊數組決定,為 O(n + m)

總的時間複雜度

  • O(m log m)
  • 邊的排序:這是算法中耗時最多的部分,時間複雜度為 O(m log m)
  • 並查集操作:對於每條邊,我們進行最多兩次的 Find 操作和可能的 Union 操作。當並查集採用了路徑壓縮優化時,這些操作的平均時間複雜度接近常數 O(α(n)),其中 α(n) 是反阿克曼函數,增長極其緩慢。因此,處理 m 條邊的時間複雜度約為 O(m * α(n)),這在實踐中可以近似看作 O(m)。
  • 綜合來看,整個算法的總時間複雜度由排序步驟主導,為 O(m log m)

Go完整代碼如下:

package main

import (
	"fmt"
	"slices"
)

// 完整的並查集模板,見我的數據結構題單
type unionFind struct {
	fa []int // 代表元
	cc int   // 連通塊個數
}

func newUnionFind(n int) unionFind {
	fa := make([]int, n)
	// 一開始有 n 個集合 {0}, {1}, ..., {n-1}
	// 集合 i 的代表元是自己
	for i := range fa {
		fa[i] = i
	}
	return unionFind{fa, n}
}

// 返回 x 所在集合的代表元
// 同時做路徑壓縮,也就是把 x 所在集合中的所有元素的 fa 都改成代表元
func (u unionFind) find(x int) int {
	// 如果 fa[x] == x,則表示 x 是代表元
	if u.fa[x] != x {
		u.fa[x] = u.find(u.fa[x]) // fa 改成代表元
	}
	return u.fa[x]
}

// 把 from 所在集合合併到 to 所在集合中
func (u *unionFind) merge(from, to int) {
	x, y := u.find(from), u.find(to)
	if x == y { // from 和 to 在同一個集合,不做合併
		return
	}
	u.fa[x] = y // 合併集合。修改後就可以認為 from 和 to 在同一個集合了
	u.cc--      // 成功合併,連通塊個數減一
}

func minCost(n int, edges [][]int, k int) int {
	if k == n {
		return 0
	}

	slices.SortFunc(edges, func(a, b []int) int { return a[2] - b[2] })
	u := newUnionFind(n)
	for _, e := range edges {
		u.merge(e[0], e[1])
		if u.cc == k {
			return e[2]
		}
	}
	panic("impossible")
}

func main() {
	n := 5
	edges := [][]int{{0, 1, 4}, {1, 2, 3}, {1, 3, 2}, {3, 4, 6}}
	k := 2
	result := minCost(n, edges, k)
	fmt.Println(result)
}

2025-12-22:最小化連通分量的最大成本。用go語言,給出一個連通的無向圖,節點編號為 0 到 n-1,邊集用數組 edges 表示,其中每條邊 edges[i] = [u, v, w] 連接_#java_02

Python完整代碼如下:

# -*-coding:utf-8-*-

from typing import List

class UnionFind:
    def __init__(self, n: int):
        self.fa = list(range(n))  # 代表元
        self.cc = n               # 連通塊個數
    
    def find(self, x: int) -> int:
        """返回 x 所在集合的代表元,同時做路徑壓縮"""
        if self.fa[x] != x:
            self.fa[x] = self.find(self.fa[x])
        return self.fa[x]
    
    def merge(self, from_: int, to: int) -> None:
        """把 from_ 所在集合合併到 to 所在集合中"""
        x, y = self.find(from_), self.find(to)
        if x == y:  # from_ 和 to 在同一個集合,不做合併
            return
        self.fa[x] = y  # 合併集合
        self.cc -= 1    # 成功合併,連通塊個數減一

def minCost(n: int, edges: List[List[int]], k: int) -> int:
    if k == n:
        return 0
    
    # 按邊權從小到大排序
    edges.sort(key=lambda e: e[2])
    uf = UnionFind(n)
    
    for e in edges:
        uf.merge(e[0], e[1])
        if uf.cc == k:
            return e[2]
    
    raise ValueError("impossible")

if __name__ == "__main__":
    n = 5
    edges = [[0, 1, 4], [1, 2, 3], [1, 3, 2], [3, 4, 6]]
    k = 2
    result = minCost(n, edges, k)
    print(result)

2025-12-22:最小化連通分量的最大成本。用go語言,給出一個連通的無向圖,節點編號為 0 到 n-1,邊集用數組 edges 表示,其中每條邊 edges[i] = [u, v, w] 連接_#golang_03

C++完整代碼如下:

#include <iostream>
#include <vector>
#include <algorithm>
#include <stdexcept>

using namespace std;

class UnionFind {
private:
    vector<int> fa; // 代表元
    int cc;         // 連通塊個數

public:
    // 構造函數
    UnionFind(int n) : fa(n), cc(n) {
        // 一開始有 n 個集合 {0}, {1}, ..., {n-1}
        // 集合 i 的代表元是自己
        for (int i = 0; i < n; i++) {
            fa[i] = i;
        }
    }

    // 返回 x 所在集合的代表元,同時做路徑壓縮
    int find(int x) {
        if (fa[x] != x) {
            fa[x] = find(fa[x]); // fa 改成代表元
        }
        return fa[x];
    }

    // 把 from 所在集合合併到 to 所在集合中
    void merge(int from, int to) {
        int x = find(from);
        int y = find(to);
        if (x == y) { // from 和 to 在同一個集合,不做合併
            return;
        }
        fa[x] = y; // 合併集合
        cc--;      // 成功合併,連通塊個數減一
    }

    // 獲取當前連通塊個數
    int getCC() const {
        return cc;
    }
};

int minCost(int n, vector<vector<int>>& edges, int k) {
    if (k == n) {
        return 0;
    }

    // 按邊權從小到大排序
    sort(edges.begin(), edges.end(),
         [](const vector<int>& a, const vector<int>& b) {
             return a[2] < b[2];
         });

    UnionFind uf(n);
    for (const auto& e : edges) {
        uf.merge(e[0], e[1]);
        if (uf.getCC() == k) {
            return e[2];
        }
    }

    throw runtime_error("impossible");
}

int main() {
    int n = 5;
    vector<vector<int>> edges = {{0, 1, 4}, {1, 2, 3}, {1, 3, 2}, {3, 4, 6}};
    int k = 2;

    int result = minCost(n, edges, k);
    cout << result << endl;

    return 0;
}

2025-12-22:最小化連通分量的最大成本。用go語言,給出一個連通的無向圖,節點編號為 0 到 n-1,邊集用數組 edges 表示,其中每條邊 edges[i] = [u, v, w] 連接_#開發語言_04