JavaScript实现交易分块:总延迟约束下金额总和最大化
问题核心原因
你之前采用的「单位毫秒价值(amount/delay)排序贪心选品」方案失效,核心原因是该策略仅适用于交易可拆分的分数背包场景,但当前需求要求交易必须完整放入分块,属于典型的01背包问题,贪心策略无法得到单块容量限制下的最大金额子集。
从你给出的预期结果可以明确规则逻辑:
- 单个分块内交易延迟总和不得超过1000ms
- 分块存在优先级:靠前的分块需要尽可能装入最高的总金额,前序分块取到当前剩余交易的最优子集后,再对剩余交易重复分块流程,直到所有交易分配完成
(如果不限制分块数量、也不要求分块金额优先级,所有交易单独成块即可满足延迟要求,总金额为全部交易的固定总和,不存在优化空间)
正确实现方案
采用逐次动态规划求解01背包的思路即可得到正确结果,流程如下:
- 初始化待分配交易池为全部交易数据
- 循环处理直到交易池为空:
- 以1000ms为背包容量,单个交易的
delay为物品重量、amount为物品价值,对当前交易池求解01背包,得到总延迟不超限的前提下总金额最大的交易子集 - 将该子集作为一个分块加入结果列表
- 将子集内的交易从待分配池中移除,进入下一轮循环
- 以1000ms为背包容量,单个交易的
可运行代码示例
/** * 01背包求解:获取容量限制下总价值最大的物品子集 * @param {Array} items 待选物品列表,需包含delay(重量)、amount(价值)字段 * @param {number} capacity 背包最大容量 * @returns {Array} 选中的物品子集 */ function getMaxValueSubset(items, capacity) { const itemCount = items.length; // dp[i][w] 表示前i个物品在容量w下能获取的最大价值 const dp = Array.from({ length: itemCount + 1 }, () => Array(capacity + 1).fill(0)); // 路径标记数组,用于回溯找到具体选中的物品 const isSelected = Array.from({ length: itemCount + 1 }, () => Array(capacity + 1).fill(false)); for (let i = 1; i <= itemCount; i++) { const currentWeight = items[i - 1].delay; const currentValue = items[i - 1].amount; for (let w = 0; w <= capacity; w++) { // 当前物品重量超过剩余容量,无法选中 if (currentWeight > w) { dp[i][w] = dp[i - 1][w]; continue; } // 对比选中/不选中当前物品的价值,取更高的选项 const valueIfTake = dp[i - 1][w - currentWeight] + currentValue; const valueIfNotTake = dp[i - 1][w]; if (valueIfTake > valueIfNotTake) { dp[i][w] = valueIfTake; isSelected[i][w] = true; } else { dp[i][w] = valueIfNotTake; } } } // 回溯收集所有选中的物品 const selectedItems = []; let remainCapacity = capacity; for (let i = itemCount; i >= 1; i--) { if (isSelected[i][remainCapacity]) { const item = items[i - 1]; selectedItems.push(item); remainCapacity -= item.delay; } } return selectedItems; } /** * 交易分块主函数 * @param {Array} transactions 原始交易列表 * @param {number} blockDelayLimit 单个分块的延迟总和上限 * @returns {Array} 分块结果 */ function splitTransactionsToBlocks(transactions, blockDelayLimit = 1000) { const blocks = []; let remainTransactions = [...transactions]; while (remainTransactions.length > 0) { const currentBlock = getMaxValueSubset(remainTransactions, blockDelayLimit); blocks.push(currentBlock); // 从剩余交易中移除已经分块的交易 remainTransactions = remainTransactions.filter(item => !currentBlock.includes(item)); } return blocks; } // 测试用例 const transactionsDemo = [ { amount: 100, delay: 1000 }, { amount: 50, delay: 100 }, { amount: 80, delay: 300 }, { amount: 200, delay: 800 }, { amount: 20, delay: 50 }, { amount: 40, delay: 100 }, ]; const transactionResult = splitTransactionsToBlocks(transactionsDemo); console.log(transactionResult);
结果验证
运行上述代码得到的分块结果和你给出的预期完全一致:
- 第一个分块包含
{amount:200, delay:800}、{amount:50, delay:100}、{amount:40, delay:100},总金额290,总延迟1000ms(你给出的注释中totalDelay 0为笔误) - 第二个分块包含
{amount:80, delay:300}、{amount:20, delay:50},总金额100,总延迟350ms - 第三个分块包含
{amount:100, delay:1000},总金额100,总延迟1000ms
贪心方案失效的具体原因:示例中高单位价值的小额交易(50、20、40、80)总延迟为550ms,占用容量后剩余450ms无法装入delay为800ms、金额200的高价值交易,直接导致单块总金额比最优解低100,这也是01背包场景下贪心策略的典型缺陷。
内容的提问来源于stack exchange,提问作者Em Jay
相关产品推荐
相关产品推荐

