难度: Hard
Submission
运行时间: 34 ms
内存: 16.3 MB
class Solution: def strobogrammaticInRange(self, low: str, high: str) -> int: def count(num: str): def flip(s): match = {"0":"0", "1":"1", "6":"9", "8":"8", "9":"6"} ans = "" for ch in s: ans += match[ch] return ans @cache def dfs(pos: int, path: str, limit: bool, start: int) -> int: digits = [0,1,6,8,9] if pos == len(num): return int(start >= 0 and path in {"0","1","8"}) ans = 0 if start >= 0: if 2 * (pos-start) >= len(num)-start: if not limit: if (len(num)-start) % 2 == 1: return int(path in {"0","1","8"}) else: return 1 else: s = path if len(num) % 2 == 0: s = s + flip(path[len(path)-1::-1]) else: s = s + flip(path[len(path)-2::-1]) return int(s <= num) else: for d in digits: if limit and d > int(num[pos]): break newLimit = limit and d == int(num[pos]) if newLimit: ans += dfs(pos+1, path+str(d),True, start) else: ans += dfs(pos+1, str(d), False, start) else: ans += dfs(pos+1, "", False, -1) for d in digits: if d == 0: continue if limit and d > int(num[pos]): break newLimit = limit and d == int(num[pos]) if newLimit: ans += dfs(pos+1, path+str(d), True, pos) else: ans += dfs(pos+1, str(d), False, pos) return ans return dfs(0, "", True, -1) + 1 #print(count(high)) #print(count(low)) ans = count(high) - count(low) match = {"0":"0", "1":"1", "6":"9", "8":"8", "9":"6"} for i in range((len(low)+1)//2): if low[i] not in match or low[len(low)-1-i] != match[low[i]]: break else: ans += 1 return ans
Explain
本题使用DFS+记忆化搜索的方法来统计在给定范围内的中心对称数的个数。主要思路是:从高位到低位依次构造中心对称数,在构造的过程中,根据已经构造的前缀判断剩余部分是否能够构成中心对称数,如果可以直接计算出个数,避免重复搜索。同时使用记忆化存储已经搜索过的状态,降低时间复杂度。
时间复杂度: O(n * 5^(n/2))
空间复杂度: O(n * 5^(n/2))
```python class Solution: def strobogrammaticInRange(self, low: str, high: str) -> int: def count(num: str): def flip(s): match = {"0":"0", "1":"1", "6":"9", "8":"8", "9":"6"} # 定义翻转映射 ans = "" for ch in s: ans += match[ch] return ans @cache def dfs(pos: int, path: str, limit: bool, start: int) -> int: digits = [0,1,6,8,9] # 可选的数字 if pos == len(num): # 已经构造完整个数字 return int(start >= 0 and path in {"0","1","8"}) # 判断构造的数是否为中心对称数 ans = 0 if start >= 0: # 已经开始构造中心对称数 if 2 * (pos-start) >= len(num)-start: # 已经构造了一半以上的数字 if not limit: # 没有上界限制 if (len(num)-start) % 2 == 1: # 数字长度为奇数 return int(path in {"0","1","8"}) # 判断当前构造的数是否为中心对称数 else: # 数字长度为偶数 return 1 # 只有一种可能 else: # 有上界限制 s = path if len(num) % 2 == 0: # 数字长度为偶数 s = s + flip(path[len(path)-1::-1]) # 补全右半部分 else: # 数字长度为奇数 s = s + flip(path[len(path)-2::-1]) # 补全右半部分 return int(s <= num) # 判断构造的数是否在范围内 else: # 还未构造一半的数字 for d in digits: if limit and d > int(num[pos]): # 超出上界限制,停止搜索 break newLimit = limit and d == int(num[pos]) # 更新上界限制条件 if newLimit: # 当前位取到上界 ans += dfs(pos+1, path+str(d),True, start) else: # 当前位未取到上界 ans += dfs(pos+1, str(d), False, start) else: # 还未开始构造中心对称数 ans += dfs(pos+1, "", False, -1) # 跳过当前位 for d in digits: if d == 0: # 不能以0开头 continue if limit and d > int(num[pos]): # 超出上界限制,停止搜索 break newLimit = limit and d == int(num[pos]) # 更新上界限制条件 if newLimit: # 当前位取到上界 ans += dfs(pos+1, path+str(d), True, pos) else: # 当前位未取到上界 ans += dfs(pos+1, str(d), False, pos) return ans return dfs(0, "", True, -1) + 1 ans = count(high) - count(low) # 计算范围内的中心对称数个数 match = {"0":"0", "1":"1", "6":"9", "8":"8", "9":"6"} for i in range((len(low)+1)//2): if low[i] not in match or low[len(low)-1-i] != match[low[i]]: break else: ans += 1 # 如果下界本身是中心对称数,需要加1 return ans ```
Explore
在中心对称数的定义中,数字必须在旋转180度后仍能表示同一个数或有效的对应数。数字2,3,4,5和7旋转后无法得到有效的数字,因此不包含在中心对称数中。有效的中心对称数数字只包括0, 1, 6, 8, 9,其中6和9旋转后互为对方,0、1、8旋转后保持不变。
在提供的`flip`函数中,如果输入的数字字符串包含非`0,1,6,8,9`的字符,将会引发一个KeyError,因为这些字符没有在翻转映射`match`字典中定义。当前的实现没有错误处理机制来处理这种情况,所以在使用`flip`函数之前,需要确保所有字符都是有效的中心对称数字符。
`limit`参数在递归函数`dfs`中用来标识当前的数字构造是否受到原始数字的位限制。如果`limit`为`True`,则当前位置的数字不能超过原始数字对应位置的数字;如果为`False`,则可以自由选择`0,1,6,8,9`中的任一数字。这个参数确保在构造数字时,只有当完全符合原始上界数字的前缀时,才会继续严格匹配上界;否则可以较为自由地构造数字。
使用`@cache`装饰器可以自动处理记忆化的细节,如存储已经计算的结果和在后续调用中直接返回这些结果,从而减少代码复杂性和出错的可能性。相比于手动管理记忆化数组,`@cache`提供了一种更简洁和易于维护的方式来实现记忆化,特别是在涉及多个参数的递归函数中这一点尤为重要。它自动地为每个不同的参数组合创建一个缓存条目,避免了复杂的索引和数据结构管理。