Python defaultdict使用字符串键时的异常行为原因咨询
问题背景
- 如果该问题此前已有相关解答还望见谅,我很难用一句话描述清楚该场景,因此此前搜索未找到有效结果。
- 我在尝试解决LeetCode 1048 最长字符串链问题,自认为代码逻辑通顺,但实际运行结果始终不符合预期。
- 调试时我发现一个反常现象:只要在循环中加入访问
dp[newWord]的语句(哪怕只是写一句不做任何输出和计算的dp[newWord]),代码就能输出正确结果,否则始终返回错误值。 - 我此前大量使用过
defaultdict,从未遇到过这类问题,希望知道该现象的成因。
对应的问题代码如下:
import collections def longestStrChain(words): words.sort(key=lambda word: (len(word), word)) dp = collections.defaultdict(int) result = 1 for word in words: if len(word) == 1: dp[word] = 1 else: for i in range(len(word)): newWord = str(word[:i] + word[i+1:]) # dp[newWord] # 取消注释这行代码就能正常运行 if newWord in dp: dp[word] = max(dp[word], dp[newWord] + 1) result = max(result, dp[word]) else: dp[word] = 1 return result print(longestStrChain(["a","b","ba","bca","bda","bdca"])) # 预期输出4,不取消注释时始终返回3
问题成因
这根本不是defaultdict的特殊玄学行为,是两个逻辑错误叠加导致的巧合:
初始值赋值位置错误
你把dp[word] = 1的初始值赋值写在了删除字符的循环内部,只要某一个删除位置得到的newWord不在dp里,就会把之前循环迭代算出来的更长的链长度直接覆盖成1。
以测试用例里的最长词bdca为例,循环到删除最后一个字符得到bdc时,bdc不在dp中,就会触发else分支,把之前算好的dp["bdca"] = 4直接重置成1,最终返回结果自然不对。提前访问
dp[newWord]刚好歪打正着屏蔽了上面的bugdefaultdict的核心特性是:只要访问不存在的key,就会自动用默认值初始化该key并插入字典。你加的那句dp[newWord]在if newWord in dp判断之前执行,等于不管newWord之前是否存在,访问之后它必然已经在dp里了,else分支永远不会触发,自然也就不会出现循环内重置dp[word]为1的问题,代码看起来就"正常运行"了。
修复方案
把dp[word]的初始值赋值移到循环外面,不要在遍历删除位置的循环里重置它即可:
import collections def longestStrChain(words): words.sort(key=lambda word: len(word)) dp = collections.defaultdict(int) result = 1 for word in words: dp[word] = 1 # 每个词的初始链长只在这里设置一次 for i in range(len(word)): newWord = word[:i] + word[i+1:] if newWord in dp: dp[word] = max(dp[word], dp[newWord] + 1) result = max(result, dp[word]) return result
内容的提问来源于stack exchange,提问作者Breezy
相关产品推荐
相关产品推荐

