题目解析
题目要求我们计算一个数字(如 12258)的所有可能翻译方式,其中翻译规则如下:
- 数字
0对应字符'a',数字1对应字符'b',以此类推,直到数字25对应字符'z'。 - 数字串可以通过不同的方式进行翻译,一些数字可能被单独翻译,也可以和相邻的数字组合翻译。
例如,对于数字 12258,它可以通过以下几种方式进行翻译:
- "bccfi"
- "bwfi"
- "bczi"
- "mcfi"
- "mzi"
因此,总共有 5 种翻译方式。
思路分析
我们可以使用动态规划来解决这个问题。定义 dp[i] 为前 i 个数字的翻译方式数量。那么,dp[i] 的值可以通过以下两种情况来递推:
-
当前数字单独翻译:我们可以将当前数字(
s[i-1])单独翻译为一个字符,因此dp[i]至少等于dp[i-1]。 -
当前数字与前一个数字组合翻译:如果当前数字和前一个数字可以组合成一个合法的翻译(即它们的值在 10 到 25 之间),则我们也可以把这两个数字当作一个整体进行翻译,这时
dp[i]还需要加上dp[i-2]。
动态规划递推式:
dp[i] = dp[i-1] + dp[i-2](如果s[i-2:i]的值在 10 到 25 之间)
初始条件:
dp[0] = 1:空字符串只有 1 种翻译方式(就是什么也不翻译)。dp[1] = 1:单个字符有 1 种翻译方式。
代码实现
def solution(num):
# 转换为字符串,方便处理每一位
s = str(num)
n = len(s)
# dp[i]表示前i个数字的翻译方法数
dp = [0] * (n + 1)
dp[0] = 1 # 空字符串有1种翻译方法
dp[1] = 1 # 第一个数字只有1种翻译方法
for i in range(2, n + 1):
# 当前数字可以单独翻译
dp[i] = dp[i-1]
# 检查当前数字能否与前一个数字组合翻译
two_digits = int(s[i-2:i])
# 组合的数字必须在10-25之间才能翻译
if 10 <= two_digits <= 25:
dp[i] = dp[i-2] + dp[i-2]
return dp[n]
if __name__ == "__main__":
# 测试用例
print(solution(12258)) # 输出 5
print(solution(1400112)) # 输出 6
print(solution(2110101)) # 输出 10
print(solution(25)) # 输出 2
print(solution(1023)) # 输出 4
代码详解
-
转换为字符串:
- 为了方便处理每一位数字,我们首先将输入的数字转换成字符串。
-
初始化
dp数组:dp[i]表示前i个数字的翻译方法数。初始化时,dp[0] = 1,dp[1] = 1。
-
动态规划递推:
- 从
i=2开始,逐个计算每个位置的翻译方式数。 - 如果当前位置的数字可以单独翻译,就继承前一个位置的翻译方式数。
- 如果当前位置的数字和前一个数字组合成一个合法的翻译(即
10 <= two_digits <= 25),就加上前两个位置的翻译方式数。
- 从
-
返回结果:
- 最终的翻译方法数保存在
dp[n]中,其中n是输入数字的长度。
- 最终的翻译方法数保存在
时间复杂度:
动态规划的时间复杂度是 O(n),其中 n 是数字的长度,因为我们只需要遍历一次数字。
空间复杂度:
空间复杂度是 O(n),我们需要一个长度为 n+1 的数组来保存中间结果。
示例解析
如果理解的不是很清晰,可以代入一个示例看一看:
示例 1:num = 12258
- 初始
dp = [1, 1, 0, 0, 0, 0] - 计算过程:
dp[2] = dp[1] = 1,因为数字1可以翻译为'b'。dp[3] = dp[2] = 1,数字2可以翻译为'c'。dp[4] = dp[3] + dp[2] = 2,数字5可以翻译为'f',同时25可以翻译为'z'。dp[5] = dp[4] + dp[3] = 3,数字8可以翻译为'i'。
- 结果:
dp[5] = 5。
优化思路
-
空间优化的关键:原来的
dp数组中,每个dp[i]只与dp[i-1]和dp[i-2]相关。因此,我们只需要记录dp[i-1]和dp[i-2],而不是保存整个数组。 -
定义两个变量:
prev2:表示dp[i-2]。prev1:表示dp[i-1]。
-
计算过程:通过循环更新
prev1和prev2来得到当前dp[i],最终得到dp[n]。
空间优化版本的代码实现
def solution(num):
# 转换为字符串,方便处理每一位
s = str(num)
n = len(s)
# 边界条件:当数字为空时或只有一位时
if n == 0:
return 0
if n == 1:
return 1
# prev2 和 prev1 分别表示 dp[i-2] 和 dp[i-1]
prev2, prev1 = 1, 1 # 初始化 dp[0] = 1, dp[1] = 1
for i in range(2, n + 1):
# 当前数字可以单独翻译
current = prev1
# 检查当前数字能否与前一个数字组合翻译
two_digits = int(s[i-2:i])
if 10 <= two_digits <= 25:
current += prev2
# 更新 prev2 和 prev1
prev2 = prev1
prev1 = current
return prev1
# 测试用例
print(solution(12258)) # 输出 5
print(solution(1400112)) # 输出 6
print(solution(2110101)) # 输出 10
print(solution(25)) # 输出 2
print(solution(1023)) # 输出 4
空间复杂度分析
- 这个优化版本的空间复杂度是
O(1),因为我们只使用了常数级别的空间(prev2和prev1),不再需要一个完整的数组来存储所有的dp状态。
时间复杂度分析
- 这个版本的时间复杂度依然是
O(n),因为我们仍然需要遍历整个数字字符串,只是我们不再需要额外的空间来存储所有的状态。