中心对称数 III

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`提供了一种更简洁和易于维护的方式来实现记忆化,特别是在涉及多个参数的递归函数中这一点尤为重要。它自动地为每个不同的参数组合创建一个缓存条目,避免了复杂的索引和数据结构管理。