题目链接
题目描述
一条包含字母 A-Z 的消息通过以下映射进行了 编码 :
‘A’ -> “1”
‘B’ -> “2”
…
‘Z’ -> “26”
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为:
“AAJF” ,将消息分组为 (1 1 10 6)
“KJF” ,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。
给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。
题目数据保证答案肯定是一个 32 位 的整数。
题目示例
示例1
输入:s = “12”
输出:2
解释:它可以解码为 “AB”(1 2)或者 “L”(12)。
示例2
输入:s = “226”
输出:3
解释:它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。
示例3
输入:s = “06”
输出:0
解释:“06” 无法映射到 “F” ,因为存在前导零(“6” 和 “06” 并不等价)。
题目提示
- 1 <= s.length <= 100
- s 只包含数字,并且可能包含前导零。
解题思路
这道题是一个经典的动态规划问题,因此我们首先要考虑状态转移方程
状态转移方程
拿到字符串的每一个字符非常繁琐,我们先将字符串转换为字符数组
char[] chs = s.toCharArray();
紧接着,我们定义和字符数组长度相同的dp数组,存放i位置的编码数量
int[] dp = new int[n];
dp[i]代表了只考虑数组的前i个位置的编码数量有多少,我们可以按照爬楼梯的思想,将其分为两部分考虑,一部分是chs[i]单独解码,另一种是chs[i - 1]和chs[i - 2]两个一起解码
初始化dp数组
由于需要dp[i]和dp[i - 1],因此我们需要初始化dp[0]和dp[1],这样就可以从dp[2]开始进行状态转移,否则的话会出现数组越界
dp[0]显然就是chs[0]是否能够单独解码,判断其是否大于等于1,小于等于9
而如果字符串只有一位字符,那么直接返回即可
if(chs[0] != '0'){
dp[0] = 1;
}
if(n == 1){
return dp[0];
}
而dp[1]则有两种情况,如果chs[0]和chs[1]都能单独解码成功,则加一,如果chs[0] 和 chs[1]能组合解码成功,则再加一
int num1 = (chs[0] - '0') * 10 + (chs[1] - '0');
if(chs[0] != '0' && chs[1] != '0'){
if(num1 >= 10 && num1 <= 26){
dp[1] = 2;
} else {
dp[1] = 1;
}
} else {
if(num1 >= 10 && num1 <= 26){
dp[1] = 1;
} else {
dp[1] = 0;
}
}
最后直接返回dp[n - 1]即可
完整代码
package week15;
public class Solution1 {
public int numDecodings(String s) {
char[] chs = s.toCharArray();
int n = chs.length;
int[] dp = new int[n];
//初始化
if(chs[0] != '0'){
dp[0] = 1;
}
if(n == 1){
return dp[0];
}
int num1 = (chs[0] - '0') * 10 + (chs[1] - '0');
if(chs[0] != '0' && chs[1] != '0'){
if(num1 >= 10 && num1 <= 26){
dp[1] = 2;
} else {
dp[1] = 1;
}
} else {
if(num1 >= 10 && num1 <= 26){
dp[1] = 1;
} else {
dp[1] = 0;
}
}
//状态转移
for(int i = 2; i < n; i++){
if(chs[i] != '0'){
dp[i] += dp[i - 1];
}
int num2 = (chs[i - 1] - '0') * 10 + (chs[i] - '0');
if(num2 >= 10 && num2 <= 26){
dp[i] += dp[i - 2];
}
}
return dp[n - 1];
}
}
优化代码
由于我们至少需要初始化dp数组的前两位,而代码中初始化dp[1]时候的代码和后面的迭代代码的冗余度非常高,所以我们可以将dp数组增加一位
这一位称之为辅助结点,只要确保后续的迭代是正确的值即可
当dp[0]是1时候,后续迭代是正确的,而dp[1]的初始化就是判断chs[0]是否能单独解码成功
由于我们前面增加了一位,因此后续的迭代中所有判断chs[i]的地方都换成chs[i - 1],最后返回dp[n]即可
优化后代码
package week15;
public class Solution2 {
public int numDecodings(String s) {
char[] chs = s.toCharArray();
int n = chs.length;
int[] dp = new int[n + 1];
dp[0] = 1;
//初始化
if(chs[0] != '0'){
dp[1] = 1;
}
//状态转移
for(int i = 2; i <= n; i++){
if(chs[i - 1] != '0'){
dp[i] += dp[i - 1];
}
int num2 = (chs[i - 2] - '0') * 10 + (chs[i - 1] - '0');
if(num2 >= 10 && num2 <= 26){
dp[i] += dp[i - 2];
}
}
return dp[n];
}
}