一、【LeetCode 面试题46】把数字翻译成字符串
1. 题目描述
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示: 0 <= num < 2^31
2. 解题思路
动态规划还没学明白,先想到的是递归,相对于容易理解一点的思路:
- 首先把数字转换成字符串;
- (1)然后先判断递归函数 backtrack 的出口:当下标等于数字的长度时说明已经得到了一个结果,将 self.count 加一然后return即可
(2)接着处理一位数的情况:将当前位置的字符转换成对应的数字,可以使用:a = ord(s[index]) - ord('0')
,也可以直接写为:a = int(s[index])
,然后不断递归调用该函数,注意index要相应加一;
(3)一位数处理完了,就可以处理两位数的情况了,获取两位数的方法很简单:b = a*10 + ord(s[index+1]) - ord('0')
(将index位作为十位,index+1位作为个位拼在一起即可),得到之后要判断该两位数的合法性,即是否大于等于10,并且小于等于25:if b >= 10 and b <= 25:
,只有满足该条件,才能进行下一步递归。
3. 代码
class Solution:
def translateNum(self, num: int) -> int:
self.count = 0
s = str(num)
self.backtrack(s, 0, len(s))
return self.count
def backtrack(self, s, index, length):
if index == length:
self.count += 1
return
a = ord(s[index]) - ord('0')
self.backtrack(s, index + 1, length) # 处理一位数的情况
if index < length - 1:
b = a*10 + ord(s[index+1]) - ord('0')
if b >= 10 and b <= 25:
self.backtrack(s, index + 2, length) # 能组成两位数
4. 打印出所有方案的代码
看题的时候就在想,我能不能把所有可能的结果都输出呢?答案当然是可以的,可以理解为回溯呀,在上面的代码上做小小的改动即可——
(1)我们使用一个 hash_map 来建立(0->‘a’,1->‘b’,……,25->‘z’)这些数字到相应字母之间的映射,可以直接写出来,也可以通过循环完成。
(2)然后在递归函数里加入一个 track,保存相应的路径值,剩下的跟上面是一模一样啦 ^ _ ^
class Solution:
def translateNum(self, num):
self.hash_map, self.res = {}, []
s = str(num)
for i in range(26):
self.hash_map[i] = chr(97 + i)
self.backtrack(s, '', 0, len(s))
return self.res
def backtrack(self, s, track, index, length):
if index == length:
# print(track)
self.res.append(track)
return
a = ord(s[index]) - ord('0')
self.backtrack(s, track + self.hash_map[a], index + 1, length)
if index < length - 1:
b = a * 10 + ord(s[index+1]) - ord('0')
if b >= 10 and b <= 25:
self.backtrack(s, track + self.hash_map[b], index + 2, length)
if __name__ == '__main__':
t = Solution()
num = 12258
print(t.translateNum(num))
输出:['bccfi', 'bczi', 'bwfi', 'mcfi', 'mzi']
二、【LeetCode 91】解码方法
1. 题目描述
一条包含字母 A-Z 的消息通过以下方式进行了编码:
‘A’ -> 1
‘B’ -> 2
…
‘Z’ -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
示例 1:
输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2:
输入: "226"
输出: 3
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
2. 出错的思路
乍一看,这不是和上一题【把数字翻译成字符串】一模一样吗?很激动的敲完了代码,再一细看发现有点不一样,上一题的翻译的数字范围是0~25, 而本题是从1~26,也就是说若0单独出现,或者出现在开头都是不算的。想了一下,在处理一位数时加了一个判断条件:if a >= 1
,这样就能保证数字是从1开始的了。
class Solution:
def numDecodings(self, s: str) -> int:
if not s:
return 0
self.res = 0
nums = list(s)
self.backtrack(nums, 0)
return self.res
def backtrack(self, nums, index):
if index == len(nums):
self.res += 1
return
a = int(nums[index]) # 先处理一位数的情况
if a >= 1:
self.backtrack(nums, index+1)
if index+1 < len(nums):
b = a * 10 + int(nums[index+1])
if b >= 10 and b <= 26:
self.backtrack(nums, index+2)
然而事情并没有我想象的那么简单,开心的提交了之后我惊讶的发现,居然超时了!!!
所以回溯行不通,还是乖乖地用动态规划吧~
3. 动态规划求解
class Solution:
def numDecodings(self, s: str) -> int:
if not s:
return 0
n = len(s)
dp = [0 for i in range(n + 1)]
if s[0] == '0':
return 0
dp[0], dp[1] = 1, 1
for i in range(1, n):
if s[i] != '0':
dp[i+1] = dp[i]
num = 10 * int(s[i-1]) + int(s[i])
if 10 <= num <= 26:
dp[i+1] += dp[i-1]
return dp[n]
参考国际站更简洁一点的写法:
class Solution:
def numDecodings(self, s: str) -> int:
if not s:
return 0
n = len(s)
dp = [0 for i in range(n + 1)]
if s[0] == '0':
return 0
dp[0], dp[1] = 1, 1
for i in range(2, n+1):
if int(s[i-1:i]) > 0:
dp[i] = dp[i-1]
if 10 <= int(s[i-2:i]) <= 26:
dp[i] += dp[i-2]
return dp[n]