Python中列表append()与copy()调用顺序不同导致的结果差异问题
动态规划最短组合求和代码结果异常问题分析
问题现象
实现「输入目标值与候选数字数组,返回和为目标值的最短数字组合」的动态规划逻辑时,两版代码仅调整了copy()与append()方法的调用顺序,运行结果完全不同:
- 第一版代码错误返回结果
[5, 1, 1, 1] - 调整顺序后的第二版代码正确返回预期结果
[5, 3]
根本原因
问题核心是Python列表属于可变对象,变量存储的是列表的内存引用,而非列表本身的数值副本,第一版代码直接修改了memo中缓存的原始列表对象,导致缓存污染,后续递归计算全部拿到错误的中间值。
具体逻辑拆解:
- 递归调用返回的
remainderCombination,多数场景下是直接从memo字典中取出的已缓存结果,你拿到的只是这个列表的引用,不是独立的新列表。 - 第一版代码先执行
remainderCombination.append(i),相当于直接修改了memo中存储的原列表:比如原本memo中缓存了target=3对应的最短组合是[3],某次递归拿到这个引用后直接append(1),memo里的缓存就被篡改成为[3,1],后续所有依赖target=3缓存的计算都会拿到错误值。 - 等append操作完成后再执行
copy(),复制的已经是被篡改过的列表,自然无法得到正确的最短组合结果。
第二版代码逻辑正确的原因也很简单:
拿到递归返回的remainderCombination后,第一时间执行copy()生成一个完全独立的新列表对象,后续的append操作全部在这个私有副本上执行,从头到尾不会修改memo中存储的原始缓存值。memo中始终保留正确的最短组合结果,后续递归取数时拿到的都是正确中间值,计算结果自然符合预期。
额外注意事项
你当前代码还存在另一个Python可变对象的经典陷阱:函数定义处写的
def howSum(target, arr, memo={}),其中默认参数memo会在函数定义阶段就完成初始化,多次调用函数如果不手动传入memo参数,所有调用会共享同一个字典对象,也就是知名的「最小惊讶原则与可变默认参数」问题。本次测试你手动传入了空字典{}因此没有触发这个问题,实际使用时建议将默认参数设为None,在函数内部初始化空字典,避免跨调用的缓存污染。
核心代码差异对比
错误版本操作顺序:
remainderCombination = howSum(target-i, arr, memo) if remainderCombination is not None: # 直接修改原缓存对象,污染全局缓存 remainderCombination.append(i) # 复制的是已经被篡改的错误值 combination = remainderCombination.copy()
正确版本操作顺序:
remainderCombination = howSum(target-i, arr, memo) if remainderCombination is not None: # 先创建独立副本,与原缓存对象完全脱离关联 combination = remainderCombination.copy() # 仅修改当前上下文的私有副本,不影响缓存 combination.append(i)
内容的提问来源于stack exchange,提问作者R.Squallo
相关产品推荐
相关产品推荐

