2026-05-15:合并有序列表的最小成本。用go语言,给定一个二维整数数组 lists,其中每个 lists[i] 都是非空且按非递减顺序排列的整数序列

网易专栏1周前发布 nxnqh
15 0 0

🤖 AI总结

主题

合并有序列表的最小成本算法问题

摘要

通过状态压缩DP和预计算中位数,求解合并有序列表的最小总费用,时间复杂度O(2ⁿ×L+3ⁿ)。

关键信息

  • 1 合并费用公式为长度和加中位数绝对差
  • 2 使用状态压缩DP和预计算优化
  • 3 n≤12,总长度≤2000

2026-05-15:合并有序列表的最小成本。用go语言,给定一个二维整数数组 lists,其中每个 lists[i] 都是非空且按非递减顺序排列的整数序列。

你可以反复进行操作:每次从 lists 里选出两个不同的序列 a = lists[i] 和 b = lists[j],然后把它们合并成一个新序列。该次合并的费用为:

len(a) + len(b) + abs(median(a) – median(b))

其中:len(x) 表示序列长度;median(x) 表示中位数(先假定序列不变有序,取中间位置;若长度为偶数,则取左侧中间那个元素)。

进行合并后,需要把原来的 a 和 b 从 lists 中移除,并把合并后的有序结果重新插入到 lists 的任意位置。一直重复上述操作,直到 lists 中只剩下一个序列。

任务是:计算把所有子序列最终合并成一个整体有序序列所需的最小总费用,并返回该最小成本。

2 <= lists.length <= 12。

1 <= lists[i].length <= 500。

-1000000000 <= lists[i][j] <= 1000000000。

lists[i] 按照非递减顺序排序。

lists[i].length 的总和不超过 2000。

输入: lists = [[1,3,5],[2,4],[6,7,8]]。

输出: 18。

解释:

合并 a = [1, 3, 5] 和 b = [2, 4]:

len(a) = 3,len(b) = 2

median(a) = 3,median(b) = 2

cost = len(a) + len(b) + abs(median(a) – median(b)) = 3 + 2 + abs(3 – 2) = 6

此时 lists 变为 [[1, 2, 3, 4, 5], [6, 7, 8]]。

合并 a = [1, 2, 3, 4, 5] 和 b = [6, 7, 8]:

len(a) = 5,len(b) = 3

median(a) = 3,median(b) = 7

cost = len(a) + len(b) + abs(median(a) – median(b)) = 5 + 3 + abs(3 – 7) = 12

此时 lists 变为 [[1, 2, 3, 4, 5, 6, 7, 8]],总成本为 6 + 12 = 18。

题目来自力扣3801。

分步详细过程+复杂度分析 一、基础概念铺垫

1.合并规则:每次选两个有序列表合并为新有序列表,费用 = 长度和 + 两个列表中位数的绝对差

  • 2.中位数定义:有序列表,偶数长度取左侧中间元素,奇数长度取正中间元素

  • 3.二进制掩码:用一个整数表示选中的列表子集(如3个列表,掩码101表示选中第0、2个列表)

  • 4.总目标:把所有列表合并为1个,求最小总费用

    二、算法整体执行步骤(对应你的代码逻辑) 第一步:输入拆分(分治预处理)

    因为列表总数n≤12,直接全量计算效率低,代码将所有列表平分为前后两半

    • 示例输入:[[1,3,5],[2,4],[6,7,8]],n=3

  • • 前半部分m=1个列表:[[1,3,5]]

  • • 后半部分n-m=2个列表:[[2,4],[6,7,8]]

    第二步:子集合并预处理(calcSorted函数)

    前半部分、后半部分分别计算所有子集的合并结果

    1. 遍历每一个列表,用二进制掩码标记子集

  • 2. 对每一个子集,将其中所有原始列表合并为一个完整的有序数组

  • 3. 存储:每个掩码(子集)对应 → 该子集合并后的完整有序数组

    具体效果:

    • 前半部分(1个列表):所有子集(共2¹=2个)都能算出合并后的有序数组

  • • 后半部分(2个列表):所有子集(共2²=4个)都能算出合并后的有序数组

  • • 作用:后续任意组合前后子集,都能直接拿到合并后的数组,不用重复计算

    第三步:所有子集的中位数预计算

    这是核心优化步骤,提前算出任意子集合并后的中位数,避免DP中重复计算:

    1. 遍历所有可能的子集掩码(总共有2ⁿ个,n=3时共8个)

  • 2. 把每个掩码拆分为:前半部分子集 + 后半部分子集

  • 3. 拿到两部分的合并有序数组,调用「寻找两个正序数组中位数」算法

  • 4. 存储:每个子集掩码 → 该子集合并后的中位数

    关键意义:

    任意两个子集合并时,它们的中位数直接查表获取,极大提升效率。

    第四步:状态压缩动态规划(核心求解最小成本)

    动态规划定义:

    dp[mask]:将mask代表的所有列表合并为一个数组的最小总费用

  • • 目标:dp[全1掩码](所有列表合并完成的最小成本)

    DP执行步骤:

    1.初始化

    • 单个列表的掩码(如001/010/100):无需合并,费用为0

  • • 其余掩码初始化为无穷大(表示暂未计算)

    2.遍历所有子集掩码
    从小到大遍历所有掩码,保证计算大子集时,其子集已经算完:

    • 跳过单个列表的掩码

  • • 对当前掩码,拆分所有合法的两个不相交子集(j和k,j+k=当前掩码)

  • • 计算合并这两个子集的费用:dp[j] + dp[k] + 合并费用

  • • 合并费用公式:子集j长度 + 子集k长度 + |j的中位数 - k的中位数|

  • • 更新dp[mask]为所有拆分方式中的最小值

    3.最终结果
    全1掩码(所有列表合并)对应的dp值,就是最小总费用。

    第五步:示例验证(对应题目输入输出)

    输入3个列表:A[1,3,5]、B[2,4]、C[6,7,8]

    1. 合并A+B:费用=3+2+|3-2|=6

  • 2. 合并(AB)+C:费用=5+3+|3-7|=12

  • 3. 总费用=6+12=18,与算法计算结果一致。

    三、时间复杂度分析

    我们基于核心参数计算:

    • n:列表总数(≤12)

  • • L:所有列表总长度(≤2000)

    1. 子集合并预处理

    • 总子集数:2ⁿ(n≤12 → 4096)

  • • 每个子集合并:总长度O(L)

  • • 时间:O(2ⁿ × L)

    2. 中位数预计算

    • 总子集数:2ⁿ

  • • 每个中位数计算:O(logL)

  • • 时间:O(2ⁿ × logL)

    3. 动态规划核心

    • 总掩码数:2ⁿ

  • • 每个掩码拆分:O(3ⁿ)(子集拆分的经典复杂度)

  • • 单次计算:O(1)(查表)

  • • 时间:O(3ⁿ)

    总时间复杂度

    O(2ⁿ × L + 3ⁿ)

    • n≤12时:3¹²=531441,2¹²×2000=800万,计算量极小,完全满足要求。

    四、额外空间复杂度(除输入外的占用空间) 1. 存储子集合并数组

    • 总子集数:2ⁿ

  • • 每个子集存储合并后的数组:总空间O(L)

  • • 空间:O(2ⁿ + L)

    2. 存储中位数、DP数组

    • 中位数数组:O(2ⁿ)

  • • DP数组:O(2ⁿ)

    总额外空间复杂度

    O(2ⁿ + L)

    • 2¹²=4096,L≤2000,空间占用极低。

    总结

    1.核心思路:利用状态压缩DP解决小规模(n≤12)的合并最优解问题,通过分治预处理+子集预计算优化效率;

  • 2.执行流程:拆分列表→预处理所有子集合并结果→预计算所有子集中位数→DP枚举所有合并方式求最小成本;

  • 3.复杂度

    • 时间:O(2ⁿ×总长度 + 3ⁿ),n≤12时效率极高;

  • • 额外空间:O(2ⁿ + 总长度),空间占用极小。

    Go完整代码如下:

    package main

    import (
    "fmt"
    "math"
    "sort"
    )

    // 88. 合并两个有序数组(创建一个新数组)
    func merge(a, b []int) []int {
    i, n := 0, len(a)
    j, m := 0, len(b)
    res := make([]int, 0, n+m)
    for {
    if i == n {
    returnappend(res, b[j:]...)
    }
    if j == m {
    returnappend(res, a[i:]...)
    }
    if a[i] < b[j] {
    res = append(res, a[i])
    i++
    } else {
    res = append(res, b[j])
    j++
    }
    }
    }

    func calcSorted(lists [][]int) [][]int {
    u := 1 << len(lists)
    sorted := make([][]int, u)
    for i, a := range lists {
    highBit := 1 << i
    for s, b := range sorted[:highBit] {
    sorted[highBit|s] = merge(a, b)
    }
    }
    return sorted
    }

    // 4. 寻找两个正序数组的中位数
    func findMedianSortedArrays(a, b []int)int {
    iflen(a) > len(b) {
    a, b = b, a
    }

    m, n := len(a), len(b)
    i := sort.Search(m, func(i int)bool {
    j := (m+n+1)/2 - i - 2
    return a[i] > b[j+1]
    }) - 1

    j := (m+n+1)/2 - i - 2
    if i < 0 {
    return b[j]
    }
    if j < 0 {
    return a[i]
    }
    return max(a[i], b[j])
    }

    func minMergeCost(lists [][]int)int64 {
    n := len(lists)
    m := n / 2
    sorted1 := calcSorted(lists[:m])
    sorted2 := calcSorted(lists[m:])

    u := 1 << n
    half := 1< 1
    median := make ([] int , u)
    for i := 1 ; i < u; i++ {
    // 把 i 分成低 m 位和高 n-m 位
    // 低 half 位去 sorted1 中找合并后的数组
    // 高 n-half 位去 sorted2 中找合并后的数组
    median[i] = findMedianSortedArrays(sorted1[i&half], sorted2[i>>m])
    }

    f := make ([] int , u)
    for i := range f {
    if i&(i -1 ) == 0 {
    continue
    }
    f[i] = math.MaxInt
    for j := i & (i - 1 ); j > i^j; j = (j - 1 ) & i {
    k := i ^ j
    f[i] = min(f[i], f[j]+f[k]+abs(median[j]-median[k]))
    }
    f[i] += len (sorted1[i&half]) + len (sorted2[i>>m])
    }
    return int64 (f[u -1 ])
    }

    func abs(x int) int {
    if x < 0 {
    return -x
    }
    return x
    }

    func main() {
    lists := [][] int {{ 1 , 3 , 5 }, { 2 , 4 }, { 6 , 7 , 8 }}
    result := minMergeCost(lists)
    fmt.Println(result)
    }

    2026-05-15:合并有序列表的最小成本。用go语言,给定一个二维整数数组 lists,其中每个 lists[i] 都是非空且按非递减顺序排列的整数序列

    Python完整代码如下:

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

    from typing import List

    def merge(a: List[int], b: List[int]) -> List[int]:
    """88. 合并两个有序数组(创建一个新数组)"""
    i, n = 0, len(a)
    j, m = 0, len(b)
    res = []
    while True:
    if i == n:
    res.extend(b[j:])
    return res
    if j == m:
    res.extend(a[i:])
    return res
    if a[i] < b[j]:
    res.append(a[i])
    i += 1
    else:
    res.append(b[j])
    j += 1

    def calc_sorted(lists: List[List[int]]) -> List[List[int]]:
    """预计算所有子集合并后的有序数组"""
    u = 1 << len(lists)
    sorted_arr = [[] for _ in range(u)]
    for i, a in enumerate(lists):
    high_bit = 1 << i
    for s in range(high_bit):
    sorted_arr[high_bit | s] = merge(a, sorted_arr[s])
    return sorted_arr

    def find_median_sorted_arrays(a: List[int], b: List[int]) -> int:
    """4. 寻找两个正序数组的中位数(这里返回较大的那半)"""
    iflen(a) > len(b):
    a, b = b, a
    m, n = len(a), len(b)
    # 二分查找 i
    left, right = 0, m
    while left < right:
    mid = (left + right) // 2
    j = (m + n + 1) // 2 - mid - 2
    if j + 1 < n and a[mid] > b[j + 1]:
    right = mid
    else:
    left = mid + 1
    i = left - 1
    j = (m + n + 1) // 2 - i - 2
    if i < 0:
    return b[j]
    if j < 0:
    return a[i]
    return max(a[i], b[j])

    def min_merge_cost(lists: List[List[int]]) -> int:
    """计算最小合并代价"""
    n = len(lists)
    m = n // 2
    sorted1 = calc_sorted(lists[:m])
    sorted2 = calc_sorted(lists[m:])
    u = 1 << n
    half = (1 << m) - 1
    median = [0] * u
    for i in range(1, u):
    median[i] = find_median_sorted_arrays(
    sorted1[i & half],
    sorted2[i >> m]
    )
    f = [0] * u
    INF = float('inf')
    for i in range(u):
    if i & (i - 1) == 0:
    continue
    f[i] = INF
    j = i & (i - 1)
    while j > (i ^ j):
    k = i ^ j
    f[i] = min(f[i], f[j] + f[k] + abs(median[j] - median[k]))
    j = (j - 1) & i
    f[i] += len(sorted1[i & half]) + len(sorted2[i >> m])
    return f[u - 1]

    def main():
    lists = [[1, 3, 5], [2, 4], [6, 7, 8]]
    result = min_merge_cost(lists)
    print(result)

    if __name__ == "__main__":
    main()

    2026-05-15:合并有序列表的最小成本。用go语言,给定一个二维整数数组 lists,其中每个 lists[i] 都是非空且按非递减顺序排列的整数序列

    C++完整代码如下:

      
    




    using namespace std;

    // 88. 合并两个有序数组(创建一个新数组)
    vector merge(const vector& a, const vector& b) {
    int i = 0, n = a.size();
    int j = 0, m = b.size();
    vector res;
    res.reserve(n + m);

    while (true) {
    if (i == n) {
    res.insert(res.end(), b.begin() + j, b.end());
    return res;
    }
    if (j == m) {
    res.insert(res.end(), a.begin() + i, a.end());
    return res;
    }
    if (a[i] < b[j]) {
    res.push_back(a[i]);
    i++;
    } else {
    res.push_back(b[j]);
    j++;
    }
    }
    }

    // 预计算所有子集合并后的有序数组
    vector int >> calcSorted( const vector int >>& lists) {
    int u = 1 << lists.size();
    vector int >> sorted(u);

    for ( int i = 0 ; i < lists.size(); i++) {
    int highBit = 1 << i;
    for ( int s = 0 ; s < highBit; s++) {
    sorted[highBit | s] = merge(lists[i], sorted[s]);
    }
    }

    return sorted;
    }

    // 4. 寻找两个正序数组的中位数(这里返回较大的那半)
    int findMedianSortedArrays(vector< int > a, vector< int > b) {
    if (a.size() > b.size()) {
    swap(a, b);
    }

    int m = a.size(), n = b.size();

    // 二分查找 i(等价于 Go 的 sort.Search)
    int left = 0 , right = m;
    while (left < right) {
    int mid = (left + right) / 2 ;
    int j = (m + n + 1 ) / 2 - mid - 2 ;
    if (j + 1 < n && a[mid] > b[j + 1 ]) {
    right = mid;
    } else {
    left = mid + 1 ;
    }
    }

    int i = left - 1 ;
    int j = (m + n + 1 ) / 2 - i - 2 ;

    if (i < 0 ) return b[j];
    if (j < 0 ) return a[i];
    return max(a[i], b[j]);
    }

    // 计算最小合并代价
    long long minMergeCost( const vector int >>& lists) {
    int n = lists.size();
    int m = n / 2 ;

    vector int >> sorted1 = calcSorted(
    vector int >>(lists.begin(), lists.begin() + m)
    );
    vector int >> sorted2 = calcSorted(
    vector int >>(lists.begin() + m, lists.end())
    );

    int u = 1 << n;
    int half = ( 1 << m) - 1 ;

    vector< int > median(u);
    for ( int i = 1 ; i < u; i++) {
    median[i] = findMedianSortedArrays(
    sorted1[i & half],
    sorted2[i >> m]
    );
    }

    vector f(u, 0 );
    for ( int i = 0 ; i < u; i++) {
    if ((i & (i - 1 )) == 0 ) {
    continue ;
    }
    f[i] = LLONG_MAX;
    for ( int j = i & (i - 1 ); j > (i ^ j); j = (j - 1 ) & i) {
    int k = i ^ j;
    f[i] = min(f[i], f[j] + f[k] + abs(median[j] - median[k]));
    }
    f[i] += sorted1[i & half].size() + sorted2[i >> m].size();
    }

    return f[u - 1 ];
    }

    int main() {
    vector int >> lists = {{ 1 , 3 , 5 }, { 2 , 4 }, { 6 , 7 , 8 }};
    long long result = minMergeCost(lists);
    cout << result << endl;
    return 0 ;
    }

    2026-05-15:合并有序列表的最小成本。用go语言,给定一个二维整数数组 lists,其中每个 lists[i] 都是非空且按非递减顺序排列的整数序列

    我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。

    © 版权声明

    相关文章