剑指Offer_#46_把数字翻译成字符串
Contents
题目
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示:
0 <= num < 231
思路分析
题意的核心在于,如果数字当中的某两位组成的数字在10~25的范围内,我们就有这两位数字就会有两种翻译。例如12258当中的前2位,即12
,可以翻译为
- bc(1和2分别翻译)
- m(12作为整体来翻译)
这是一个递归问题。可以这样理解,假设数字最后一位的索引是n
,递归问题就是recur(n)
,即求解以索引n
为结尾的数字的翻译方式。
这个问题可以分解为递归子问题,且分解的过程需要进行分类讨论:
- 如果
n
和n-1
位置的数字组合起来在10~25范围内:recur(n) = recur(n-1) + recur(n-2)
- 如果
n
和n-1
位置上的数字组合起来不在10~25范围内:recur(n) = recur(n-1)
看这个递归形式,有没有觉得似曾相识?这一题其实是跳台阶一题的扩展。引用评论区大佬一句很好的总结:
- 《斐》、《青蛙》、《翻译》,三道题,难度越来越大,为什么呢?并不是算法难了,而是题目变得抽象了。
- 《斐》和《青蛙》是无条件的往后跳,而《翻译》是有条件的往后跳,我们要做的就是找到需要的判断条件(即[10, 25])。
明白了上面说的,其实代码编写与跳台阶一题也很相似,仅仅是增加了一个条件判断。
我们通过递归的思维从上到下的分析问题,编写代码时,为了避免重复计算,可以从下到上地解决问题,即动态规划,从dp[0]
开始计算,直到dp[n]
,就可以得到结果。
解答
class Solution {
public int translateNum(int num) {
//先将数字转换为字符串
String str = String.valueOf(num);
//注意dp数组和字符串是错位的,多出一个dp[0]
int[] dp = new int[str.length() + 1];
//初始化动态规划dp数组
dp[0] = 1;
dp[1] = 1;
//根据dp状态转移方程进行迭代
//这里的索引值最大是字符串长度,因为substring()方法不包含结束索引
for(int i = 2;i <= str.length();i++){
String tmp = str.substring(i - 2, i);
if(tmp.compareTo("10") >= 0 && tmp.compareTo("25") <= 0)
dp[i] = dp[i - 1] + dp[i - 2];
else
dp[i] = dp[i - 1];
}
//返回最后一个元素
return dp[str.length()];
}
}
dp数组与数字字符串的对应关系
注意dp[]的索引与字符串的索引是相差1的。
dp[0]
的推导
当前两位数字在10~25范围内时,有dp[2] = dp[1] + dp[0]
,其中dp[2] = 2,dp[1] = 1
,所以dp[0] = 1