← 返回编程题库
coding-knapsack-symbol-selection-budget中等免费版2000ms未尝试

预算约束下的标的选择(0/1 背包)

Budget-Constrained Ticker Selection (0/1 Knapsack)

开始编码

组合经理手头有固定的现金预算和一份筛选后的标的池。每个标的最多买一股、单股成本是已知整数;研究模型给每个标的标了一个整数的预期利润。她想找一个标的子集——每个买一股——使总成本不超预算、总预期利润最大。这是经典 0/1 背包 问题:每个标的是 yes/no 决策(不允许分数股、不允许同一标的买两份),cost 是重量、profit 是价值、现金预算是容量。

请实现 solution(items, budget)items 是 dict 列表 {"symbol": str, "cost": int, "profit": int}budget 是非负整数。返回 dict {"max_profit": int, "selected": list[str]},其中 selected 是被选中的 symbol 排序后列表。

当多个子集都能达到最大利润时,tie-break 规则:取字典序最小的排序后选中列表 —— 按 symbol 升序顺序处理物品,只要纳入它后剩余物品仍能在剩余预算内凑齐最大利润目标,就纳入它。 这一规则确定性、可重现,并匹配「利润相同时偏好包含字典序更小符号的子集」的直觉。

示例

solution([{"symbol": "NVDA", "cost": 2, "profit": 3}, {"symbol": "MSFT", "cost": 3, "profit": 4}, {"symbol": "TSLA", "cost": 4, "profit": 5}, {"symbol": "AMZN", "cost": 5, "profit": 6}], 5) 返回 {"max_profit": 7, "selected": ["MSFT", "NVDA"]}。经典小背包:成本 2+3=5 恰好等于预算,利润 3+4=7,比任意单一物品(单标的最大利润 6 来自成本为 5 的 AMZN)都好。

solution([{"symbol": "AAPL", "cost": 300, "profit": 50}, {"symbol": "GOOG", "cost": 250, "profit": 40}], 100) 返回 {"max_profit": 0, "selected": []}。两个标的的成本都超预算,无法买入;最优是持币不动,利润 0、选中集为空。

solution([{"symbol": "ZZZZ", "cost": 5, "profit": 10}, {"symbol": "AAAA", "cost": 5, "profit": 10}], 5) 返回 {"max_profit": 10, "selected": ["AAAA"]}。预算只够买一个,两个候选利润相同 → tie-break 选字典序更小的 AAAA。

函数骨架见 stubs/stub.py

应用背景

预算约束下的子集选择是组合构建的核心问题:每个零售券商 App、每个 robo-advisor 的开户引导、每个 PM 的资产配置工具,归根到底都在回答某种形态的「给定我的现金和一份候选清单,开哪些仓?」此处的 0/1 背包归约(每个标的 1 股、整数成本和利润、硬预算上限)刻意做了简化 —— 真实生产组合优化器还要叠加分数股、最小手数、行业上限、交易成本、Beta 中性、换手率惩罚等。但这个最简形态恰好是面试白板题的基本骨架,也是更复杂优化器的内核:dp 递推完全相同,只是状态空间变大。Tie-break 取字典序最小不只是装饰 —— 生产系统要求确定性输出,相同输入跨重跑、跨审计、跨单测必须给出一致的选股结果,这也是「先按符号排序、再回溯 dp」这个习惯值得养成的原因。掌握了这套二维 dp + traceback 的骨架,多重背包(行业桶)、有界背包(最小手数)、value-cost-ratio 贪心给 branch-and-bound 当上界等扩展,都能水到渠成。

约束条件

  • 0 ≤ len(items) ≤ 100
  • 0 ≤ cost ≤ 200(每个物品)
  • 0 ≤ profit ≤ 1000(每个物品)
  • 0 ≤ budget ≤ 5000
  • 每个物品是一个 dict,含键 'symbol' (str), 'cost' (int), 'profit' (int)
  • 同一 items 列表内 symbol 唯一且区分大小写
  • Tie-break(利润相同时):取字典序最小的选中列表 —— 按符号升序遍历物品,剩余目标可达即取
  • 输出 dict:{'max_profit': int, 'selected': list[str] 按字母升序}

样例

Case 1 · empty items list

输入: [[],100]

期望: {"max_profit":0,"selected":[]}

items 为空 → 0 利润,[],无可选标的。

Case 2 · single item fits in budget

输入: [[{"symbol":"AAPL","cost":50,"profit":30}],100]

期望: {"max_profit":30,"selected":["AAPL"]}

唯一标的 AAPL 成本 50 ≤ 预算 100,必选;利润 30。

Case 3 · single item exceeds budget

输入: [[{"symbol":"AAPL","cost":150,"profit":30}],100]

期望: {"max_profit":0,"selected":[]}

AAPL 成本 150 > 预算 100,无法买入;最优 0 利润空集。

Case 4 · two items exact-fit budget

输入: [[{"symbol":"AAPL","cost":40,"profit":20},{"symbol":"GOOG","cost":60,"profit":30}],100]

期望: {"max_profit":50,"selected":["AAPL","GOOG"]}

AAPL+GOOG 总成本 40+60=100 恰好等于预算;总利润 50。

Case 5 · tie-break — equal profit, lex-smaller wins

输入: [[{"symbol":"ZZZZ","cost":5,"profit":10},{"symbol":"AAAA","cost":5,"profit":10}],5]

期望: {"max_profit":10,"selected":["AAAA"]}

两标的同成本同利润,预算只够买一个;按 tie-break 规则取字典序更小的 AAAA。

最近提交

还没有提交记录。

编码区

实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。

加载编辑器...
计时0:00

默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。

Case 1 · empty items list

输入: [[],100]

期望: {"max_profit":0,"selected":[]}

items 为空 → 0 利润,[],无可选标的。

Case 2 · single item fits in budget

输入: [[{"symbol":"AAPL","cost":50,"profit":30}],100]

期望: {"max_profit":30,"selected":["AAPL"]}

唯一标的 AAPL 成本 50 ≤ 预算 100,必选;利润 30。

Case 3 · single item exceeds budget

输入: [[{"symbol":"AAPL","cost":150,"profit":30}],100]

期望: {"max_profit":0,"selected":[]}

AAPL 成本 150 > 预算 100,无法买入;最优 0 利润空集。

Case 4 · two items exact-fit budget

输入: [[{"symbol":"AAPL","cost":40,"profit":20},{"symbol":"GOOG","cost":60,"profit":30}],100]

期望: {"max_profit":50,"selected":["AAPL","GOOG"]}

AAPL+GOOG 总成本 40+60=100 恰好等于预算;总利润 50。

Case 5 · tie-break — equal profit, lex-smaller wins

输入: [[{"symbol":"ZZZZ","cost":5,"profit":10},{"symbol":"AAAA","cost":5,"profit":10}],5]

期望: {"max_profit":10,"selected":["AAAA"]}

两标的同成本同利润,预算只够买一个;按 tie-break 规则取字典序更小的 AAAA。