第89篇 LeetCode剑指Offer动态规划(六)把数字翻译成字符串
1.题目描述
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", “bwfi”, “bczi”, “mcfi"和"mzi”
提示:
0 <= num < 231
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
2.动态规划的解题步骤
2.1.dp[i]的定义
对上题而言,我们举一个数字,如12258,那么dp[12258]就表示12258这个数字有几种翻译方式
dp[i]就表示i这个数字有几种翻译方式。
2.2.递推式
如12258:自顶向下思想,因为字符对应的数字有一位数和两位数,所以如果我们先把8翻译成一个字符,那么12258的翻译方式就是1225的翻译方式,而如果取58(假定可以翻译),那么12258的翻译方式就是122的翻译方式,那么12258总的翻译方式就是1225的翻译方式和122的翻译方式的和。因此我们可以初步断定:
dp[i] = dp[i / 10] + dp[i / 100];
看一个数字:102
按照上面的方法dp[102] = dp[10] + dp[1] = 3;
但是102只有两种:bac,ic
因此如果num % 100 < 10 或者num % 100 > 25的时候,num / 100是不能够去单独计算的,这样就会造成重复计算了里面的个位数。比如102重复计算了2:,所以改成
dp[i] = dp[i / 10] +(i % 100 > 9 && i % 100 < 26) ? dp[i / 100] : 0;
2.3.dp初始化和递归函数出口
如果数字只有一位,直接返回1,只有一种翻译方法。
if(num < 10) {
return 1;
}
2.4.递归函数
非常的简洁漂亮。简洁的代码干的事非常多。
int opt_recursion(int num) {
if(num < 10) {
return 1;
}
int count1 = opt_recursion(num / 10);
int count2 = 0;
if(num % 100 > 9 && num % 100 < 26) {
count2 = opt_recursion(num / 100);
}
return count1 + count2;
}
这道题的递归非常快,反而迭代慢了。
2.5.递归改成迭代
每个数字至少有一种翻译方式
int opt_iterate(int num) {
vector<int> dp(num, 1);
int n = 1;
while(n <= num) {
int count1 = dp[n / 10];
int count2 = 0;
if(n % 100 > 9 && n % 100 < 26) {
count2 = dp[n / 100];
}
dp[n - 1] = count1 + count2;
n++;
}
return dp[num - 1];
}
超时了,因为数字太大,O(n)就大了,超时,比如12258就执行12258次。
2.6.迭代优化
用哈希表记录num中数字的情况,每一种数字组成的情况。
这样时间复杂度就是O(num.size()),12258就执行5次就行了。
int opt_iterate(int num) {
unordered_map<int,int> dp;
dp[0] = 1;
deque<int> nums;
int n = num;
while(n > 0) {
nums.push_front(n % 10);
dp[n % 10] = 1;
n /= 10;
}
int i = 0;
int len = nums.size();
int currnum = 0;
for(int i = 0;i < len;i++) {
currnum = currnum * 10 + nums[i];
int count1 = dp[currnum / 10];
int count2 = 0;
if(currnum % 100 > 9 && currnum % 100 < 26) {
count2 = dp[currnum / 100];
}
dp[currnum] = count1 + count2;
}
return dp[num];
}
继续空间优化:
每次使用的都是num / 10和num / 100的数据来更新num的翻译次数,因此只需记录这两个值就行了;
int opt_iterate_better(int num) {
deque<int> nums;
int n = num;
while(n > 0) {
nums.push_front(n % 10);
n /= 10;
}
int ans = 1;
int ans_model_ten = 1;
int ans_model_hundred = 1;
int currnum = 0;
for(int value: nums) {
currnum = currnum * 10 + value;
int count1 = ans_model_ten;
int count2 = 0;
if(currnum % 100 > 9 && currnum % 100 < 26) {
count2 = ans_model_hundred;
}
ans = count1 + count2;
ans_model_hundred = ans_model_ten;
ans_model_ten = ans;
}
return ans;
}
3.结语
能做出来后,优化就方便多了,记得自顶向下。