剑指 Offer 46. 把数字翻译成字符串
难度:middle\\color{orange}{middle}middle
其实就是有条件的 青蛙跳格子 问题。
题目描述
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示:
- 0<=num<2310 <= num < 2^{31}0<=num<231
算法
(动态规划) O(n)O(n)O(n)
翻译的过程可以分为两种情况:
- 首先我们可以把每一位单独翻译
- 然后我们考虑组合某些连续的两位
那么我们可以归纳出翻译的规则,字符串的第 iii 位置:
- 可以单独作为一位来翻译
- 如果第 i−1i−1i−1 位和第 iii 位组成的数字在
10
到25
之间,可以把这两位连起来翻译。含有前导零的两位数不在题目规定的翻译规则中,比如[1, 4, 02]
这种翻译是不对的。
我们可以用 f(i)f(i)f(i) 表示以第 iii 位结尾的前缀串翻译的方案数。考虑第 iii 位单独翻译和与前一位连接起来再翻译对 f(i)f(i)f(i) 的贡献。单独翻译对 f(i)f(i)f(i) 的贡献为 f(i−1)f(i−1)f(i−1);如果第 i−1i−1i−1 位存在,并且第 i−1i−1i−1 位和第 iii 位形成的数字 xxx 满足 10≤x≤2510≤x≤2510≤x≤25,那么就可以把第 i−1i−1i−1 位和第 iii 位连起来一起翻译,对 f(i)f(i)f(i) 的贡献为 f(i−2)f(i−2)f(i−2),否则为 000。我们可以列出这样的动态规划转移方程:
f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25]f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25]f(i)=f(i−1)+f(i−2)[i−1≥0,10≤x≤25]
边界条件为 f[0]=1f[0] = 1f[0]=1,下文有解释。
复杂度分析
-
时间复杂度:O(n)O(n)O(n)
-
空间复杂度 : O(n)O(n)O(n),字符串使用额外的空间。
C++ 代码
class Solution {
public:int translateNum(int num) {string s = to_string(num);int n = s.size();vector<int> f(n + 1);f[0] = 1;for (int i = 1; i <= n; i ++) {f[i] = f[i - 1];if (i > 1) {int t = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');// 为什么大于10, 因为含有前导零的两位数不在题目规定的翻译规则中,比如02、03等等if (t >= 10 && t <= 25) f[i] += f[i - 2];}}return f[n];}
};
Q:为什么一个数字都没有的方案数是 1
?
A:f[0]
代表翻译前 0
个数字的方法数,这样的状态定义其实是没有实际意义的,但是 f[0]
的值需要保证边界是对的,即 f[1]
和 f[2]
是对的。比如说,翻译前 1
个数只有一种方法,将其单独翻译,即 f[1] = f[1 - 1] = 1
。翻译前两个数,
如果第 1
个数和第 2
个数可以组合起来翻译,那么 f[2] = f[1] + f[0] = 2
,否则只能单独翻译第 2
个数,即 f[2] = f[1] = 1
。因此,在任何情况下 f[0]
取 1
都可以保证 f[1]
和 f[2]
是正确的,所以 f[0]
应该取 1
。