题目描述
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”
解法:动态规划
根据题意,可按照下图的思路,总结出 “递推公式” (即转移方程)。
因此,此题可用动态规划解决,以下按照流程解题。
动态规划解析:
记数字 n u m num num 第 i i i 位数字为 x i x_i xi ,数字 n u m num num 的位数为 n n n ;
例如: n u m = 12258 num = 12258 num=12258 的 n = 5 n = 5 n = 5n=5 n=5n=5 , x 1 = 1 x_1 = 1 x1=1 。
-
状态定义: 设动态规划列表 dp , d p [ i ] dp[i] dp[i] 代表以 x i x_i xi 为结尾的数字的翻译方案数量。
-
转移方程: 若 x i x_i xi 和 x i − 1 x_{i-1} xi−1 组成的两位数字可以被翻译,则 d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i] = dp[i - 1] + dp[i - 2] dp[i]=dp[i−1]+dp[i−2] ;否则 d p [ i ] = d p [ i − 1 ] dp[i] = dp[i - 1] dp[i]=dp[i−1] 。
- 可被翻译的两位数区间:当
x
i
−
1
=
0
x_{i-1} = 0
xi−1=0 时,组成的两位数是无法被翻译的(例如
00
,
01
,
02
,
⋯
00, 01, 02, \cdots
00,01,02,⋯),因此区间为
[
10
,
25
]
[10, 25]
[10,25] 。
- 可被翻译的两位数区间:当
x
i
−
1
=
0
x_{i-1} = 0
xi−1=0 时,组成的两位数是无法被翻译的(例如
00
,
01
,
02
,
⋯
00, 01, 02, \cdots
00,01,02,⋯),因此区间为
[
10
,
25
]
[10, 25]
[10,25] 。
-
初始状态: d p [ 0 ] = d p [ 1 ] = 1 dp[0] = dp[1] = 1 dp[0]=dp[1]=1 ,即 “无数字” 和 “第 1 1 1 位数字” 的翻译方法数量均为 1 1 1 ;
-
返回值: d p [ n ] dp[n] dp[n] ,即此数字的翻译方案数量。
Q: 无数字情况 d p [ 0 ] = 1 dp[0]=1 dp[0]=1 从何而来?
A: 当 n u m num num 第 1 , 2 1, 2 1,2 位的组成的数字 ∈ [ 10 , 25 ] \in [10,25] ∈[10,25] 时,显然应有 2 2 2 种翻译方法,即 d p [ 2 ] = d p [ 1 ] + d p [ 0 ] = 2 dp[2] = dp[1] + dp[0] = 2 dp[2]=dp[1]+dp[0]=2 ,而显然 d p [ 1 ] = 1 dp[1] = 1 dp[1]=1 ,因此推出 d p [ 0 ] = 1 dp[0] = 1 dp[0]=1 。
方法一:字符串遍历
- 为方便获取数字的各位 x i x_i xi ,考虑先将数字 n u m num num 转化为字符串 s s s ,通过遍历 s s s 实现动态规划。
- 通过字符串切片 s [ i − 2 : i ] s[i - 2:i] s[i−2:i] 获取数字组合 10 x i − 1 + x i 10 x_{i-1} + x_i 10xi−1+xi ,通过对比字符串 ASCII 码判断字符串对应的数字区间。
- 空间使用优化: 由于 d p [ i ] dp[i] dp[i] 只与 d p [ i − 1 ] dp[i - 1] dp[i−1] 有关,因此可使用两个变量 a , b a, b a,b 分别记录 d p [ i ] , d p [ i − 1 ] dp[i], dp[i - 1] dp[i],dp[i−1] ,两变量交替前进即可。此方法可省去 d p dp dp 列表使用的 O ( N ) O(N) O(N) 的额外空间。
复杂度分析
- 时间复杂度 O ( N ) O(N) O(N) : N N N 为字符串 s s s 的长度(即数字 n u m num num 的位数 log ( n u m ) \log(num) log(num)),其决定了循环次数。
- 空间复杂度 O ( N ) O(N) O(N) : 字符串 s s s 使用 O ( N ) O(N) O(N) 大小的额外空间。
C++代码实现
class Solution {
public:
int translateNum(int num) {
string str = to_string(num);
int len = str.size();
if(len < 2) return len;
vector<int> dp(len+1);
dp[1] = 1;
dp[0] = 1;
for(int i = 2;i <= len;i++){
if(str[i-2] == '1' || (str[i-2] == '2' && str[i-1] <= '5')) dp[i] = dp[i-2]+dp[i-1];
else dp[i] = dp[i-1];
}
return dp[len];
}
};
方法二:数字求余
- 上述方法虽然已经节省了 d p dp dp 列表的空间占用,但字符串 s s s 仍使用了 O ( N ) O(N) O(N) 大小的额外空间。
空间复杂度优化:
- 利用求余运算 n u m % 10 num \% 10 num%10 和求整运算 n u m / / 10 num // 10 num//10 ,可获取数字 n u m num num 的各位数字(获取顺序为个位、十位、百位…)。
- 因此,可通过 求余 和 求整 运算实现 从右向左 的遍历计算。而根据上述动态规划 “对称性” ,可知从右向左的计算是正确的。
- 自此,字符串 s s s 的空间占用也被省去,空间复杂度从 O ( N ) O(N) O(N) 降至 O ( 1 ) O(1) O(1) 。
复杂度分析
- 时间复杂度 O ( N ) O(N) O(N) : N N N 为字符串 s s s 的长度(即数字 n u m num num 的位数 log ( n u m ) \log(num) log(num),其决定了循环次数。
- 空间复杂度 O ( 1 ) O(1) O(1) : 几个变量使用常数大小的额外空间。
C++代码实现
class Solution {
public:
int translateNum(int num) {
int a = 1, b = 1, x, y = num % 10;
while(num != 0) {
num /= 10;
x = num % 10;
int tmp = 10 * x + y;
int c = (tmp >= 10 && tmp <= 25) ? a + b : a;
b = a;
a = c;
y = x;
}
return a;
}
};