You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将集合划分为和差最小的两个子集并返回,非NP-hard解法及双列堆叠应用

两列堆叠最小化最大高度的实现方案

首先明确:该问题本质是经典的集合分区问题,严格来说不存在多项式时间的精确最优解法,属于NP-hard问题。如果你可以接受近似最优解,或者元素总规模不大,有两种完全不需要枚举所有子集的实用方案可选。

现有代码问题

你当前用的是基础的「大元素优先放入较矮列」的贪心逻辑,且判断条件存在冗余:sum(rs) + nextItem.size > sum(ls) + nextItem.size 等价于直接比较sum(rs) > sum(ls),这个逻辑在部分测试用例下会得到非最优结果,也就是你遇到的inputNotWorking的问题。

方案1:Karmarkar-Karp 最大差分贪心算法

时间复杂度仅为O(n log n),不需要枚举,对于绝大多数普通业务场景的近似精度远高于基础贪心,大部分场景下可以得到精确最优解:

// 输入按大小升序排列即可
function karmarkarKarpPartition(items) {
    // 维护堆,每个元素结构为[当前堆总大小, 堆内元素列表]
    let heap = items.map(item => [item.size, [item]]);
    // 按堆大小降序排序,模拟大顶堆
    while (heap.length > 1) {
        heap.sort((a, b) => b[0] - a[0]);
        // 取出最大的两个堆
        const first = heap.shift();
        const second = heap.shift();
        // 合并两个堆,差值作为新堆的大小,元素合并到较小的堆一侧
        const mergedSize = Math.abs(first[0] - second[0]);
        const mergedItems = first[0] >= second[0] 
            ? first[1].concat(second[1].map(i => ({...i, __side: 'right'})))
            : second[1].concat(first[1].map(i => ({...i, __side: 'right'})));
        heap.push([mergedSize, mergedItems]);
    }
    // 拆分左右子集
    const left = heap[0][1].filter(i => !i.__side);
    const right = heap[0][1].filter(i => i.__side).map(i => {
        const {__side, ...rest} = i;
        return rest;
    });
    const leftSum = sumSizes(left);
    const rightSum = sumSizes(right);
    return {
        left,
        right,
        maxHeight: Math.max(leftSum, rightSum)
    }
}

function sumSizes(itemSizeArray) {
  return itemSizeArray.reduce((prev, curr) => prev + curr.size, 0);
}

// 测试用例
let inputWorking = [
  {item: 'a', size: 5},
  {item: 'b', size: 6},
  {item: 'c', size: 7},
  {item: 'd', size: 8},
];
let inputNotWorking = [
  {item: 'a', size: 14},
  {item: 'b', size: 14},
  {item: 'c', size: 20},
  {item: 'd', size: 20},
  {item: 'e', size: 21},
];
console.log(karmarkarKarpPartition(inputWorking));
console.log(karmarkarKarpPartition(inputNotWorking)); // 输出maxHeight为48,符合预期

方案2:动态规划精确解法

如果你必须要100%的精确最优解,且所有元素的总高度S不超过10000的量级,可以用动态规划方案,时间复杂度为O(nS),远优于枚举所有子集的O(2^n),还可以回溯得到具体的子集划分:

function exactPartition(items) {
    const total = sumSizes(items);
    const target = Math.floor(total / 2);
    const n = items.length;
    // dp[i][j] 表示前i个元素能不能凑出和为j
    const dp = Array.from({length: n+1}, () => Array(target + 1).fill(false));
    dp[0][0] = true;
    for (let i = 1; i <= n; i++) {
        const size = items[i-1].size;
        for (let j = 0; j <= target; j++) {
            dp[i][j] = dp[i-1][j] || (j >= size ? dp[i-1][j - size] : false);
        }
    }
    // 找到最大的可达j
    let maxJ = 0;
    for (let j = target; j >=0; j--) {
        if (dp[n][j]) {
            maxJ = j;
            break;
        }
    }
    // 回溯得到选中的元素
    const left = [];
    let j = maxJ;
    for (let i = n; i > 0; i--) {
        const size = items[i-1].size;
        if (j >= size && dp[i-1][j - size]) {
            left.push(items[i-1]);
            j -= size;
        }
    }
    const right = items.filter(item => !left.includes(item));
    return {
        left,
        right,
        maxHeight: Math.max(sumSizes(left), sumSizes(right))
    }
}

内容的提问来源于stack exchange,提问作者Ray Suelzer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 18:15:06