1. 问题背景与需求解析
这道来自华为机考的"称砝码"题目,表面看是个简单的组合数学问题,实则考察了动态规划在实际场景中的应用能力。题目通常给出n种不同重量的砝码各若干枚,要求计算出这些砝码能够称出的所有不同重量组合。
在实际工作中,这类问题常见于物流称重、实验室测量、工业生产等需要精确计量场景。比如快递公司需要知道现有砝码组合能称量哪些包裹重量,药厂需要确认原料配比时能覆盖的称重范围。
2. 核心算法思路拆解
2.1 暴力枚举法的局限性
最直观的解法是枚举所有可能的砝码组合。假设有3种砝码,每种最多使用m个,时间复杂度高达O(m^n)。当n=10,m=10时,组合数达到100亿级,显然不可行。
实际机考中,n通常在1-10范围内,每种砝码数量不超过2000个,总重不超过20000
2.2 动态规划解法原理
采用背包问题思路,定义dp数组:
- dp[i]表示重量i能否被称出
- 初始dp[0]=true(重量0总是可达)
- 遍历每种砝码,更新可达重量状态
关键状态转移方程:
python复制for 砝码重量w in weights:
for 数量k in counts:
for j from max_weight downto w*k:
if dp[j - w*k]:
dp[j] = True
2.3 算法优化技巧
- 重量压缩:先计算所有砝码总重max_weight,dp数组只需开到max_weight
- 提前终止:当某种砝码的所有可能组合都无法产生新重量时提前跳出循环
- 砝码去重:合并相同重量的砝码,减少无效计算
3. 完整实现与代码注解
3.1 Python实现示例
python复制def count_weights(weights, counts):
max_weight = sum(w*c for w,c in zip(weights, counts))
dp = [False]*(max_weight + 1)
dp[0] = True
for w, c in zip(weights, counts):
for j in range(max_weight, -1, -1):
if dp[j]:
for k in range(1, c+1):
if j + w*k <= max_weight:
dp[j + w*k] = True
return sum(dp) - 1 # 排除重量0
3.2 关键参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
| weights | List[int] | 各砝码的单重 |
| counts | List[int] | 对应砝码的数量 |
| max_weight | int | 动态规划数组大小 |
| dp | List[bool] | 状态记录数组 |
3.3 复杂度分析
- 时间复杂度:O(n * W * k),其中n是砝码种类,W是总重量,k是平均每种砝码数量
- 空间复杂度:O(W),取决于总重量规模
4. 边界条件与特殊测试用例
4.1 常见边界情况
-
单一砝码:
- 输入:weights=[1], counts=[5]
- 输出:5(可称1,2,3,4,5)
-
零重量砝码:
- 输入:weights=[0,1], counts=[2,3]
- 应过滤掉0重量砝码
-
超大数量:
- 输入:weights=[1], counts=[2000]
- 需确保算法效率
4.2 测试用例设计策略
python复制test_cases = [
# 常规情况
([1,2,3], [2,1,1], 8),
# 所有砝码相同
([5], [4], 4),
# 包含重复重量
([1,1,2], [3,2,1], 9),
# 极值测试
([1]*10, [2000]*10, 20000)
]
5. 工程实践中的优化技巧
5.1 内存优化方案
当总重量较大时(如20000):
- 使用bitset代替bool数组,每个bit表示1g
- Python可用
bytearray或array('B')
优化后实现:
python复制def count_weights_optimized(weights, counts):
max_weight = sum(w*c for w,c in zip(weights, counts))
dp = bytearray((max_weight + 7) // 8)
dp[0] |= 1
for w, c in zip(weights, counts):
for j in range(max_weight, -1, -1):
if dp[j // 8] & (1 << (j % 8)):
for k in range(1, c+1):
if j + w*k <= max_weight:
dp[(j + w*k) // 8] |= 1 << ((j + w*k) % 8)
return bin(int.from_bytes(dp, 'big')).count('1') - 1
5.2 多语言实现对比
| 语言 | 特点 | 适用场景 |
|---|---|---|
| Python | 代码简洁,开发快 | 机考、原型验证 |
| C++ | bitset性能最优 | 嵌入式称重系统 |
| Java | 平衡开发效率与性能 | 企业级应用 |
6. 实际应用场景扩展
6.1 物流称重系统
快递公司需要根据现有砝码配置:
- 确认能称量的包裹重量范围
- 优化砝码采购组合
- 动态调整各网点砝码分配
6.2 实验室测量方案
化学实验中的精确称量:
- 最小可称量单位确定
- 误差范围计算
- 替代砝码组合方案
6.3 工业生产质量控制
汽车零件产线:
- 零件重量分拣
- 不良品自动检测
- 多工位称重协同
7. 常见问题排查指南
7.1 错误现象:结果偏少
可能原因:
- 砝码组合未考虑所有排列
- 解决:确保外层循环遍历所有砝码类型
- 动态规划更新顺序错误
- 解决:必须从大到小更新dp数组
7.2 错误现象:内存溢出
可能原因:
- 未计算max_weight直接开大数组
- 解决:先精确计算最大可能重量
- 使用普通list存储稀疏结果
- 解决:改用位运算压缩存储
7.3 性能优化检查清单
- [ ] 是否跳过了0重量砝码
- [ ] 是否合并了相同重量砝码
- [ ] 是否使用了逆序更新
- [ ] 是否设置了合理的循环边界
8. 算法扩展与变种思考
8.1 最小砝码数问题
给定目标重量,求最少需要多少个砝码:
- 转换为最少硬币问题
- 使用动态规划求min值
8.2 砝码摆放位置问题
天平两侧均可放置砝码:
- 状态增加左/右放置维度
- 结果需考虑绝对值
8.3 带约束条件的称重
如:
- 某些砝码不能同时使用
- 总使用数量限制
- 需要添加约束条件判断
在工业级实现中,这类问题通常会结合遗传算法进行多目标优化。比如在保证称重范围的前提下,同时考虑砝码采购成本、仓储空间等实际因素。