offer41的题目是给定一个数字,然后按照如下规则把它翻译为字符串:
0翻译成”a”,1翻译成”b”,……,11翻译成”l”,……,25翻译成”z”。
这样一来,一个数字可能有多个翻译——例如12258有5种不同的翻译,它们分别是”bccfi”、”bwfi”、”bczi”、”mcfi”和”mzi”。
现在给出一个数字,要求其总的翻译方法数。
递归
本来这题我是想和“最长不含重复字符的字符串”,以及“第一个只出现一次的字符”放在一起讲的,但是后来发现是完全不一样的问题。“把数字翻译成字符串”的核心在于将原问题转为两个子问题。
比如12258,可以视为1+2258或12+558这两个问题;这样一来就可以发现,问题的本质是递归。
# offer41-solution 1
def getTranslationCount(self, s):
"""
:type s: str
:rtype: int
"""
if not s: return 0
if len(s) == 1: return 1
# 判断字符是否为‘0’,避免0X的形式
if s[0] != '0' and int(s[:2]) >= 0 and int(s[:2]) <= 25:
return self.getTranslationCount(s[2:]) + self.getTranslationCount(s[1:])
return self.getTranslationCount(s[1:])
动态规划
其实本问题能想到DP的话,想必也是极好的。动态规划是自下而上解决问题,从已知的 base case 出发,存储前面的状态,迭代出最后的结果。
先定义状态:以第 i 位结尾的前缀串翻译的方案数。
转移方程:
假设翻译字符串的第
i
i
i位:可以单独作为一位来翻译
如果第
i
−
1
i−1
i−1位和第
i
i
i位组成的数字在 10 到 25 之间,可以把这两位连起来翻译。
用
f
(
i
)
f(i)
f(i)表示以第
i
i
i位结尾的前缀串翻译的方案数,然后考虑第
i
i
i位单独翻译和与前一位连接起来再翻译对
f
(
i
)
f(i)
f(i)的贡献——单独翻译对
f
(
i
)
f(i)
f(i)的贡献为
f
(
i
−
1
)
f(i - 1)
f(i−1);如果第
i
−
1
i−1
i−1位存在且
i
−
1
i−1
i−1位和
i
i
i位形成的数字
x
x
x满足
10
≤
x
≤
25
10≤x≤25
10≤x≤25,那么两个字符连起来一起翻译对
f
(
i
)
f(i)
f(i)的贡献为
f
(
i
−
2
)
f(i - 2)
f(i−2)。其余的情况为 0。
# offer41-solution 2
class Solution:
def getTranslationCount(self, number):
"""
:type number: int
:rtype: int
"""
if number < 0:
return 0
numberStr = str(number)
return self.getTranslateCount(numberStr)
def getTranslateCount(self, numberStr): # 从数组尾部出发,动态规划
length = len(numberStr)
counts = [0] * length
# count=0
for i in range(length - 1, -1, -1):
count = 0
if i < length - 1:
count += counts[i + 1] # 与前面的总计数相加
else:
count = 1
if i < length - 1:
digit1 = int(numberStr[i])
digit2 = int(numberStr[i + 1])
converted = digit1 * 10 + digit2
if converted >= 10 and converted <= 25:
if i < length - 2: # 分两种不同情况
count += counts[i + 2]
else:
count += 1
counts[i] = count
return counts[0]