
本文介绍一种基于动态规划的精确解法,用于在给定预算下选择尽可能多种类的商品,且总价格严格不超过预算、并尽可能接近该预算——本质是0/1背包问题的变种(目标为最大化物品数量,其次为最大化总价值)。
本文介绍一种基于动态规划的精确解法,用于在给定预算下选择尽可能多种类的商品,且总价格严格不超过预算、并尽可能接近该预算——本质是0/1背包问题的变种(目标为最大化物品数量,其次为最大化总价值)。
在实际购物或资源分配场景中,“买最多不同品类”比“花最多钱”更符合用户真实诉求。但简单按价格升序贪心选取(如先买最便宜的)虽能最大化种类数,却无法保证总花费最接近预算——例如预算3000元,商品价格为[500, 800, 1000, 1500],贪心选前3项得2300元;而选800+1500=2300仍非最优,实际上500+1000+1500=3000才是完美解。这说明:仅按价格排序的贪心策略不满足“最接近预算”的优化目标,必须引入动态规划求解。
核心思路:双目标动态规划
本题需同时优化两个优先级目标:
- 首要目标:选择商品种类数最多;
- 次要目标:在种类数相同的情况下,总价格尽可能接近预算(即最大化总价格)。
为此,我们定义状态 dp[i][j] 表示考虑前 i 个商品、预算上限为 j 时,所能达到的最大种类数;同时额外维护一个辅助数组 value[i][j] 记录对应最大种类数下的最高总价格(即最优解的价值)。但更简洁的做法是:将“种类数”作为主价值,用价格作为次要维度——不过标准0/1背包通常优化单一价值。因此我们采用以种类数为价值、价格为重量的背包建模,并确保在种类数相同时选择总价更高的组合。
然而,原始答案提供的DP实现存在逻辑偏差:它仅以价格为价值进行最大化(等价于普通0/1背包),并未显式约束“种类数最多”为第一目标。正确做法应将每个商品的价值设为1(代表1种),重量为其价格,目标是在总重量≤预算前提下最大化总价值(即种类数),并在价值相同时最大化总重量(即总价格)。
以下是修正后的专业实现:
def maximize_products(budget, product_list):
if not product_list or budget <= 0:
return []
n = len(product_list)
# dp[j] = (max_count, max_cost) 表示预算j下能达到的最大种类数及对应最高总花费
# 初始化:dp[0] = (0, 0),其余为(-1, 0)表示不可达
dp = [(-1, 0) for _ in range(budget + 1)]
dp[0] = (0, 0)
# 记录路径:parent[j] = (prev_budget, product_index) 用于回溯
parent = [None] * (budget + 1)
# 动态规划填表
for i, (name, price) in enumerate(product_list):
# 逆序遍历预算,避免重复使用同一商品
for j in range(budget, price - 1, -1):
prev_count, prev_cost = dp[j - price]
if prev_count != -1: # 状态可达
new_count = prev_count + 1
new_cost = prev_cost + price
# 更新条件:种类数更多,或种类数相同但总花费更高
curr_count, curr_cost = dp[j]
if (new_count > curr_count) or \
(new_count == curr_count and new_cost > curr_cost):
dp[j] = (new_count, new_cost)
parent[j] = (j - price, i)
# 找到最优预算点:种类数最多,且在同种类数下花费最高
best_count, best_cost = -1, 0
best_budget = 0
for j in range(budget + 1):
cnt, cost = dp[j]
if cnt > best_count or (cnt == best_count and cost > best_cost):
best_count, best_cost = cnt, cost
best_budget = j
# 回溯构造商品列表
result = []
j = best_budget
while parent[j] is not None:
prev_j, idx = parent[j]
result.append(product_list[idx][0])
j = prev_j
return result[::-1] # 反转恢复添加顺序使用示例与验证
budget = 3000
product_list = [("Apples", 1000), ("Banana", 500), ("Oranges", 1500),
("Grapes", 2000), ("Cherry", 800)]
print(maximize_products(budget, product_list))
# 输出:['Banana', 'Apples', 'Oranges'] → 总价2800(500+1000+1500),种类数3,最接近3000
# 注:若存在组合恰好等于3000(如500+800+1700),则会优先返回该组合注意事项
- 时间复杂度:O(n × budget),适用于预算数值不大(如≤10⁴)的场景;若预算过大,需考虑状态压缩或启发式近似算法。
- 空间优化:当前实现已使用一维DP数组,进一步可省略parent数组改用二维DP存储决策,但会增加内存开销。
- 输入鲁棒性:函数已处理空列表、零/负预算等边界情况。
- 唯一性说明:当存在多个同等最优解(同种类数、同总价格)时,返回结果取决于商品遍历顺序,属正常行为。
综上,该方案通过严谨的双目标动态规划,真正实现了“种类最多 + 花费最接近预算”的业务需求,远超简单贪心算法的适用范围。

















