Hard

题目描述

给你一个二维整数数组 lists,其中每个 lists[i] 是一个非空的整数数组,按非递减顺序排序。

你可以重复选择两个列表 a = lists[i]b = lists[j],其中 i != j,并合并它们。合并 ab 的成本为:

len(a) + len(b) + abs(median(a) - median(b)),其中 lenmedian 分别表示列表长度和中位数。

合并 ab 后,从 lists 中移除 ab,并将新的合并后的有序列表插入任意位置。重复合并直到只剩一个列表。

返回将所有列表合并为一个有序列表所需的最小总成本。

数组的中位数是按非递减顺序排序后的中间元素。如果数组有偶数个元素,中位数是左边的中间元素。

示例 1:

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

示例 2:

输入:lists = [[1,1,5],[1,4,7,8]]
输出:10

示例 3:

输入:lists = [[1],[3]]
输出:4

示例 4:

输入:lists = [[1],[1]]
输出:2

约束条件:

  • 2 <= lists.length <= 12
  • 1 <= lists[i].length <= 500
  • -10^9 <= lists[i][j] <= 10^9
  • lists[i] 按非递减顺序排序
  • lists[i].length 的总和不超过 2000

解题思路

这是一个经典的区间动态规划问题,可以用状态压缩 DP 解决。

核心思路:

  1. 由于列表数量不超过12,可以使用位掩码表示选择的列表子集
  2. 预计算所有可能子集对应的合并后列表的中位数
  3. 使用 dp[mask] 表示合并掩码 mask 中所有列表的最小成本

算法步骤:

  1. 预处理阶段:对于每个可能的掩码,计算对应列表合并后的结果和中位数
  2. 状态转移dp[mask] = min(dp[s] + dp[mask^s] + cost(s, mask^s)),其中 smask 的非空真子集
  3. 成本计算:两个子集的合并成本 = 长度之和 + 中位数差的绝对值

优化要点:

  • 使用归并排序合并两个已排序的列表
  • 预计算所有子集的合并结果避免重复计算
  • 只遍历有效的子集分割减少计算量

时间复杂度主要来自于状态数 O(2^n) 和每个状态的转移 O(2^n),总体为 O(4^n),在 n≤12 的约束下是可行的。

代码实现

class Solution {
public:
    long long minMergeCost(vector<vector<int>>& lists) {
        int n = lists.size();
        vector<vector<int>> merged(1 << n);
        vector<int> medians(1 << n);
        
        // 预计算每个掩码对应的合并结果和中位数
        for (int mask = 1; mask < (1 << n); mask++) {
            if (__builtin_popcount(mask) == 1) {
                int idx = __builtin_ctz(mask);
                merged[mask] = lists[idx];
                medians[mask] = lists[idx][(lists[idx].size() - 1) / 2];
            } else {
                // 找到最低位的1
                int lowbit = mask & (-mask);
                int remaining = mask ^ lowbit;
                
                // 合并两个已排序的列表
                vector<int>& a = merged[lowbit];
                vector<int>& b = merged[remaining];
                vector<int> result;
                
                int i = 0, j = 0;
                while (i < a.size() && j < b.size()) {
                    if (a[i] <= b[j]) {
                        result.push_back(a[i++]);
                    } else {
                        result.push_back(b[j++]);
                    }
                }
                while (i < a.size()) result.push_back(a[i++]);
                while (j < b.size()) result.push_back(b[j++]);
                
                merged[mask] = result;
                medians[mask] = result[(result.size() - 1) / 2];
            }
        }
        
        // 动态规划
        vector<long long> dp(1 << n, LLONG_MAX);
        
        // 初始化单个列表的成本为0
        for (int i = 0; i < n; i++) {
            dp[1 << i] = 0;
        }
        
        for (int mask = 1; mask < (1 << n); mask++) {
            if (__builtin_popcount(mask) == 1) continue;
            
            // 枚举所有可能的分割
            for (int submask = mask; submask > 0; submask = (submask - 1) & mask) {
                int complement = mask ^ submask;
                if (complement == 0) continue;
                
                long long cost = merged[submask].size() + merged[complement].size() + 
                               abs(medians[submask] - medians[complement]);
                
                if (dp[submask] != LLONG_MAX && dp[complement] != LLONG_MAX) {
                    dp[mask] = min(dp[mask], dp[submask] + dp[complement] + cost);
                }
            }
        }
        
        return dp[(1 << n) - 1];
    }
};
class Solution:
    def minMergeCost(self, lists: List[List[int]]) -> int:
        n = len(lists)
        merged = [[] for _ in range(1 << n)]
        medians = [0] * (1 << n)
        
        # 预计算每个掩码对应的合并结果和中位数
        for mask in range(1, 1 << n):
            if bin(mask).count('1') == 1:
                idx = (mask & -mask).bit_length() - 1
                merged[mask] = lists[idx][:]
                medians[mask] = lists[idx][(len(lists[idx]) - 1) // 2]
            else:
                # 找到最低位的1
                lowbit = mask & (-mask)
                remaining = mask ^ lowbit
                
                # 合并两个已排序的列表
                a, b = merged[lowbit], merged[remaining]
                result = []
                i = j = 0
                
                while i < len(a) and j < len(b):
                    if a[i] <= b[j]:
                        result.append(a[i])
                        i += 1
                    else:
                        result.append(b[j])
                        j += 1
                
                result.extend(a[i:])
                result.extend(b[j:])
                
                merged[mask] = result
                medians[mask] = result[(len(result) - 1) // 2]
        
        # 动态规划
        dp = [float('inf')] * (1 << n)
        
        # 初始化单个列表的成本为0
        for i in range(n):
            dp[1 << i] = 0
        
        for mask in range(1, 1 << n):
            if bin(mask).count('1') == 1:
                continue
            
            # 枚举所有可能的分割
            submask = mask
            while submask > 0:
                complement = mask ^ submask
                if complement != 0:
                    cost = len(merged[submask]) + len(merged[complement]) + abs(medians[submask] - medians[complement])
                    
                    if dp[submask] != float('inf') and dp[complement] != float('inf'):
                        dp[mask] = min(dp[mask], dp[submask] + dp[complement] + cost)
                
                submask = (submask - 1) & mask
        
        return dp[(1 << n) - 1]
public class Solution {
    public long MinMergeCost(int[][] lists) {
        int n = lists.Length;
        var merged = new List<int>[1 << n];
        var medians = new int[1 << n];
        
        // 预计算每个掩码对应的合并结果和中位数
        for (int mask = 1; mask < (1 << n); mask++) {
            if (BitOperations.PopCount((uint)mask) == 1) {
                int idx = BitOperations.TrailingZeroCount((uint)mask);
                merged[mask] = new List<int>(lists[idx]);
                medians[mask] = lists[idx][(lists[idx].Length - 1) / 2];
            } else {
                // 找到最低位的1
                int lowbit = mask & (-mask);
                int remaining = mask ^ lowbit;
                
                // 合并两个已排序的列表
                var a = merged[lowbit];
                var b = merged[remaining];
                var result = new List<int>();
                
                int i = 0, j = 0;
                while (i < a.Count && j < b.Count) {
                    if (a[i] <= b[j]) {
                        result.Add(a[i++]);
                    } else {
                        result.Add(b[j++]);
                    }
                }
                while (i < a.Count) result.Add(a[i++]);
                while (j < b.Count) result.Add(b[j++]);
                
                merged[mask] = result;
                medians[mask] = result[(result.Count - 1) / 2];
            }
        }
        
        // 动态规划
        var dp = new long[1 << n];
        Array.Fill(dp, long.MaxValue);
        
        // 初始化单个列表的成本为0
        for (int i = 0; i < n; i++) {
            dp[1 << i] = 0;
        }
        
        for (int mask = 1; mask < (1 << n); mask++) {
            if (BitOperations.PopCount((uint)mask) == 1) continue;
            
            // 枚举所有可能的分割
            for (int submask = mask; submask > 0; submask = (submask - 1) & mask) {
                int complement = mask ^ submask;
                if (complement == 0) continue;
                
                long cost = merged[submask].Count + merged[complement].Count + 
                           Math.Abs(medians[submask] - medians[complement]);
                
                if (dp[submask] != long.MaxValue && dp[complement] != long.MaxValue) {
                    dp[mask] = Math.Min(dp[mask], dp[submask] + dp[complement] + cost);
                }
            }
        }
        
        return dp[(1 << n) - 1];
    }
}
/**
 * @param {number[][]} lists
 * @return {number}
 */
var minMergeCost = function(lists) {
    const n = lists.length;
    const memo = new Map();
    
    function getMedian(arr) {
        return arr[Math.floor((arr.length - 1) / 2)];
    }
    
    function mergeTwoLists(a, b) {
        const result = [];
        let i = 0, j = 0;
        while (i < a.length && j < b.length) {
            if (a[i] <= b[j]) {
                result.push(a[i++]);
            } else {
                result.push(b[j++]);
            }
        }
        while (i < a.length) result.push(a[i++]);
        while (j < b.length) result.push(b[j++]);
        return result;
    }
    
    function getCost(a, b) {
        return a.length + b.length + Math.abs(getMedian(a) - getMedian(b));
    }
    
    function solve(currentLists) {
        if (currentLists.length === 1) return 0;
        
        const key = JSON.stringify(currentLists.map(list => list.join(',')).sort());
        if (memo.has(key)) return memo.get(key);
        
        let minCost = Infinity;
        
        for (let i = 0; i < currentLists.length; i++) {
            for (let j = i + 1; j < currentLists.length; j++) {
                const cost = getCost(currentLists[i], currentLists[j]);
                const merged = mergeTwoLists(currentLists[i], currentLists[j]);
                
                const newLists = [];
                for (let k = 0; k < currentLists.length; k++) {
                    if (k !== i && k !== j) {
                        newLists.push(currentLists[k]);
                    }
                }
                newLists.push(merged);
                
                const totalCost = cost + solve(newLists);
                minCost = Math.min(minCost, totalCost);
            }
        }
        
        memo.set(key, minCost);
        return minCost;
    }
    
    return solve(lists);
};

复杂度分析

复杂度类型说明
时间复杂度O(4^n × L)n为列表数量,L为单个列表最大长度。状态数O(2^n),每个状态转移O(2^n),合并列表O(L)
空间复杂度O(2^n × L)存储所有状态的合并结果,每个状态最多包含所有元素