题目来源:https://leetcode-cn.com/problems/decode-ways-ii/
大致题意:
给定一个由数字 0 - 9 和 * 号(可以变成 1-9 的任意数字)组成的字符串,解码回由字母 A - Z 构成的字符串,其中 A 对应 1 … Z 对应 26。于是相邻的数字可以拆开解码,也可以在小于 26 时合并解码,这样就有了多种解码方法。求出解码方法的个数
思路
既然知道了解码可以单独或者两个,于是对于当前的字符 c,可以选择直接解码,也可以选择与上一个字符组合解码。
动态规划
因为有 * 号的存在,所以字符的解码方法可能有多种,并不唯一,分析可知:
- 若当前字符单独解码:在当前字符等于 * 号时,可以有 9 种可能性;若当前字符不等于 * ,那么只要它不为 0,就会对应 1 种唯一的解码
- 若当前字符与上一个字符合并解码:若两个字符都为 *,那么有 11 - 19 和 21 - 26 共 15 种解码方法;若前一个字符为 ,那么在当前字符小于等于 6 时, 号可以为 1 或 2,即 2 种解码方法,而若当前字符大于 6,则 * 号 只能为 1;若当前字符为 *,那么在上一个字符为 1 时, * 号可以为 1 - 9,共 9 种解码,而若为 2,则 * 号 可以为 1 - 6,共 6 种解码,大于 2 时,没有可用解码方法
于是使用 f[i] 存下首位到第 i 位有的解码方法,那么:
- 若当前字符单独解码的方法有 x 种,f[i] 就等于 f[i - 1] * x
- 若当前字符与前一个字符合并解码的方法有 y 种,那么 f[i] 可以加上 f[i - 2] * y
- 于是,在当前字符不是首字符的时候就有 f[i] = f[i - 1] * x + f[i - 2] * y
- 再加上边界初始化, f[0] = 1, 即长度为 0 的空串只能返回空串,一种解码;f[-1] = 0,即长度 -1,不存在
代码:
public static final int MOD = 1000000007;
public int numDecodings(String s) {
int n = s.length();
// 使用三个变量存下 f_i-2 f_i-1 f_i,节约内存
// 用 long 防止溢出
long pre = 0;
long medium = 1;
long last = 0;
// 遍历字符串,动态规划
for (int i = 0; i < n; i++) {
last = medium * decodeOneChar(s.charAt(i)) % MOD;
if (i != 0) {
last = (last + pre * decodeTwoChar(s.charAt(i-1), s.charAt(i)) % MOD ) % MOD;
}
pre = medium;
medium = last;
}
return (int)last;
}
// 考虑对一个字符的解码
public int decodeOneChar(char c) {
if (c == '0') {
return 0;
}
return c == '*' ? 9 : 1;
}
// 考虑对两个字符的解码
public int decodeTwoChar(char c1, char c2) {
if (c1 == '0') {
return 0;
}
if (c1 == '*') {
if (c2 == '*') {
return 15;
} else {
return c2 <= '6' ? 2 : 1;
}
}
if (c2 == '*') {
if (c1 == '1') {
return 9;
} else if (c1 == '2') {
return 6;
} else {
return 0;
}
}
// 都为常数
return ((c1 - '0') * 10 + (c2 - '0')) <= 26 ? 1: 0;
}