问题描述
小M获得了一个任务,需要将数字翻译成字符串。翻译规则是:0对应"a",1对应"b",依此类推直到25对应"z"。一个数字可能有多种翻译方法。小M需要一个程序来计算一个数字有多少种不同的翻译方法。
例如:数字12258可以翻译成 "bccfi", "bwfi", "bczi", "mcfi" 和 "mzi",共5种方式。
样例1:
输入:
num = 12258
输出:5
解题思路一:动态规划
-
定义状态:设
dp[i]表示数字num的前i位有多少种不同的翻译方法。 -
状态转移方程:对于每一位数字
num[j](其中j从1到n,n是num的位数),我们有两种情况:- 如果
num[j]不是0,那么它只能单独作为一个字符翻译,所以dp[j] = dp[j-1]。 - 如果
num[j]是0,那么它可以与前一位数字组合起来作为一个字符翻译(如果前一位数字存在且在1到25之间),所以dp[j] = dp[j-1] + dp[j-2](假设num[j-1]是一个有效的字符)。
- 如果
-
初始化:
dp[0] = 1(空字符串有一种翻译方法),dp[1] = 1(如果第一位数字有效,它有一种翻译方法)。 -
计算结果:遍历数字的每一位,根据状态转移方程更新
dp数组,最后dp[n]就是数字num的总翻译方法数。
解题思路二:递归加记忆化
-
定义函数:定义一个函数
f(i),表示从数字num的第i位开始有多少种不同的翻译方法。 -
递归情况:
- 如果
i等于0,返回1,因为空字符串有一种翻译方法。 - 如果
num[i]是0,那么它可以与前一位数字组合起来翻译,所以f(i) = f(i-1) + f(i-2)(如果i > 1且num[i-1]是一个有效的字符)。 - 如果
num[i]不是0,那么它只能单独作为一个字符翻译,所以f(i) = f(i-1)。
- 如果
-
记忆化:为了避免重复计算,我们可以使用一个数组或哈希表来存储已经计算过的
f(i)的值。 -
计算结果:从
f(n)开始,其中n是num的位数,递归地计算每个f(i)的值,直到f(1)和f(0)。
本题我选用动态规划解法详细如下:
-
定义状态:
dp[i]表示数字num的前i位有多少种不同的翻译方法。 -
初始化:
dp[0] = 1,表示空字符串有一种翻译方法。 -
遍历数字:从第一位数字开始遍历到第
n位数字。 -
状态转移:
- 对于每一位数字,
dp[i]的值至少等于dp[i-1],因为当前位可以独立作为一个字符。 - 如果当前位和前一位可以组合成一个有效的字符(即两位数在10到25之间),则
dp[i]还需要加上dp[i-2]的值,因为这两位可以组合成一个字符。
- 对于每一位数字,
-
结果:
dp[n]即为整个数字num的翻译方法总数。
public static int solution(int num) {
// 将整数转换为字符串
String s = String.valueOf(num);
// 获取字符串长度
int n = s.length();
// 初始化动态规划数组,长度为n+1,因为dp的索引从0开始
int[] dp = new int[n + 1];
// 初始化dp[0]为1,表示空字符串有一种翻译方法
dp[0] = 1;
// 遍历字符串的每一位数字
for (int i = 1; i <= n; i++) {
// 当前位至少有一种翻译方法,即前一位的翻译方法数
dp[i] = dp[i - 1];
// 如果当前位不是第一位,可以检查是否可以与前一位组合
if (i > 1) {
// 提取当前位和前一位组成的两位数字符串
String twoDigits = s.substring(i - 2, i);
// 检查两位数是否在10到25之间
if (twoDigits.compareTo("10") >= 0 && twoDigits.compareTo("25") <= 0) {
// 如果是,则当前位的翻译方法数还需要加上前两位的翻译方法数
dp[i] += dp[i - 2];
}
}
}
// 返回整个数字的翻译方法总数
return dp[n];
}