1. 问题背景与需求分析
这道来自华为机考的"称砝码"问题,表面看似简单,实则考察了多重编程思维。题目通常给出n种不同重量的砝码各若干枚,要求计算出用这些砝码能称出多少种不同的重量组合。这类问题在实际场景中广泛存在于物流称重、实验室测量、工业生产等领域。
举个例子,假设有1g和2g两种砝码,数量分别为2个和1个,那么可以组合出:0g(不放砝码)、1g、2g(1+1或单个2)、3g(1+2)、4g(1+1+2)共5种重量。这就是我们需要通过程序实现的组合计算。
2. 核心算法解析
2.1 动态规划解决方案
这类组合问题最适合采用动态规划(DP)来解决。DP的核心思想是将大问题分解为小问题,通过解决小问题来构建大问题的解。
我们定义一个布尔型数组dp,其中dp[i]表示重量i是否可以被称出。初始化时dp[0]=true(表示0重量总是可达),其余为false。然后遍历每种砝码,更新dp数组。
python复制def count_weights(weights, counts):
total = sum(w*c for w,c in zip(weights, counts))
dp = [False]*(total+1)
dp[0] = True
for w, c in zip(weights, counts):
for i in range(total, -1, -1):
if dp[i]:
for k in range(1, c+1):
if i + w*k <= total:
dp[i + w*k] = True
return sum(dp)
2.2 算法优化思路
上述基础实现有三个关键优化点:
- 逆向遍历重量范围,避免同一砝码被重复计算
- 提前计算最大可能重量,减少无效计算
- 使用集合代替数组可以节省空间,但可能略微降低速度
3. 完整实现与测试
3.1 Python实现代码
python复制def solve():
n = int(input())
weights = list(map(int, input().split()))
counts = list(map(int, input().split()))
possible = {0}
for w, c in zip(weights, counts):
temp = set()
for cnt in range(1, c+1):
for p in possible:
temp.add(p + w * cnt)
possible.update(temp)
print(len(possible))
if __name__ == "__main__":
solve()
3.2 测试用例设计
好的测试用例应该包含:
- 边界情况:只有一种砝码
- 极端情况:砝码数量很多
- 常规情况:多种砝码组合
示例测试:
code复制输入:
2
1 2
2 1
输出:
5
4. 性能分析与优化
4.1 时间复杂度分析
设砝码种类为m,最大数量为c,最大重量为W:
- 最坏时间复杂度为O(m × c × W)
- 空间复杂度为O(W)
4.2 实际优化技巧
- 使用位运算加速:可以用位掩码表示可达重量
- 提前终止:当所有可能重量都被覆盖时可提前结束
- 砝码预处理:先按重量排序可以减少计算量
优化后的实现:
python复制def count_weights_optimized(weights, counts):
possible = 1 # 使用bitmask,最低位代表0g
for w, c in zip(weights, counts):
new_possible = possible
for _ in range(c):
new_possible |= possible << w
possible = new_possible
return bin(possible).count('1')
5. 常见问题与调试技巧
5.1 典型错误排查
- 结果偏小:通常是因为正向遍历导致重复计算
- 结果偏大:可能是砝码数量计算错误
- 超时问题:需要检查是否有不必要的嵌套循环
5.2 调试建议
- 打印中间结果:输出每次迭代后的可达重量集合
- 小规模测试:先用简单案例验证基础逻辑
- 性能分析:使用time模块测量各部分的执行时间
6. 实际应用扩展
这个问题可以延伸出多个变种:
- 限制称重次数
- 砝码只能放在天平的一侧
- 考虑砝码的组合价值最大化
工业中的实际应用场景包括:
- 药品配比称重
- 货物装载重量组合计算
- 实验材料精确测量
在解决这类问题时,最重要的是理解动态规划的状态转移过程。我个人的经验是,先用小规模数据手工计算,确保完全理解问题本质后再开始编码。另外,在面试场景中,清晰地解释算法思路往往比直接写代码更重要。