leetcode-639-解码方法II
[博客链接]
[题目链接]
[github地址]
[题目描述]
一条包含字母 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" 不同。
除了 上面描述的数字字母映射方案,编码消息中可能包含 '*' 字符,可以表示从 '1' 到 '9' 的任一数字(不包括 '0')。例如,编码字符串 "1*" 可以表示 "11"、"12"、"13"、"14"、"15"、"16"、"17"、"18" 或 "19" 中的任意一条消息。对 "1*" 进行解码,相当于解码该字符串可以表示的任何编码消息。
给你一个字符串 s ,由数字和 '*' 字符组成,返回 解码 该字符串的方法 数目 。
由于答案数目可能非常大,返回对 109 + 7 取余 的结果。
示例 1:
输入:s = "*"
输出:9
解释:这一条编码消息可以表示 "1"、"2"、"3"、"4"、"5"、"6"、"7"、"8" 或 "9" 中的任意一条。
可以分别解码成字符串 "A"、"B"、"C"、"D"、"E"、"F"、"G"、"H" 和 "I" 。
因此,"*" 总共有 9 种解码方法。
示例 2:
输入:s = "1*"
输出:18
解释:这一条编码消息可以表示 "11"、"12"、"13"、"14"、"15"、"16"、"17"、"18" 或 "19" 中的任意一条。
每种消息都可以由 2 种方法解码(例如,"11" 可以解码成 "AA" 或 "K")。
因此,"1*" 共有 9 * 2 = 18 种解码方法。
示例 3:
输入:s = "2*"
输出:15
解释:这一条编码消息可以表示 "21"、"22"、"23"、"24"、"25"、"26"、"27"、"28" 或 "29" 中的任意一条。
"21"、"22"、"23"、"24"、"25" 和 "26" 由 2 种解码方法,但 "27"、"28" 和 "29" 仅有 1 种解码方法。
因此,"2*" 共有 (6 * 2) + (3 * 1) = 12 + 3 = 15 种解码方法。
提示:
- 1 <= s.length <= 105
- s[i] 是 0 - 9 中的一位数字或字符 '*'
思路一: 动态规划
- 本题的dp思路还是比较清晰的
- 难点在于整理一个不漏不重的分组讨论方案
- 定义一个dp[]数组:dp[i]表示以第i个字符为结尾的方案数量
- 接下来开始分组讨论:
- 因为本题未明确用例一定可以解码,所以需要一定的corner case
- 当出现连续的0或者开头为0的时候直接返回0
- 接下来就是非常复杂的分类讨论了
- 当前位 cur为*
- 需要判断pre位为什么
- 如果pre为*:
- i >= 2 :dp[i] = dp[i - 2] * 15;(对应11-19,21-26这15中数字)
- dp[i] = 15;
- pre = '1'
- i >= 2 :dp[i] = dp[i - 2] * 9;
- dp[i] = 9;
- pre = '2'
- i >= 2 :dp[i] = dp[i - 2] * 6;
- dp[i] = 6;
- 如果pre为*:
- 其余情况dp[i]+=dp[i-1]*9即可
- 需要判断pre位为什么
- 当前位 cur为0
- 满足解码要求的情况
- pre = '*' : i >=2?dp[i]= dp[i-2]*2:2;
- dp[i] = i>=2?dp[i-2]:1;
- 满足解码要求的情况
- 当前位 cur为其他字符
- pre = 0: dp[i] = dp[i - 1];
- pre = 1: i >=2?dp[i] = dp[i - 1] + dp[i - 2];:dp[i-1]+1
- pre = 2:
- cur <= 6: i >=2?dp[i] = dp[i - 1] + dp[i - 2]*2;:dp[i-1]+1
- cur > 6: dp[i] = dp[i - 1];
- pre = *:
- cur <= 6: i >=2?dp[i] = dp[i - 1] + dp[i - 2];:dp[i-1]+1
- cur > 6: dp[i] = dp[i - 1];
- 其余情况: dp[i] = dp[i - 1];
- 最后因为数值很大需要做mod处理即可
- 我这个判断逻辑有些复杂,可以做一个空格位减少边界判断
public int numDecodings(String s) {
if (s.startsWith("0") || s.contains("00")) {
return 0;
}
int MOD = (int) 1e9 + 7;
long[] dp = new long[s.length()];
dp[0] = s.charAt(0) == '*' ? 9 : 1;
for (int i = 1; i < s.length(); i++) {
char cur = s.charAt(i), pre = s.charAt(i - 1);
if (cur == '*') {
if (pre == '*') {
if (i >= 2) {
dp[i] = dp[i - 2] * 15;
} else {
dp[i] = 15;
}
}
if (pre == '1') {
if (i >= 2) {
dp[i] = dp[i - 2] * 9;
} else {
dp[i] = 9;
}
}
if (pre == '2') {
if (i >= 2) {
dp[i] = dp[i - 2] * 6;
} else {
dp[i] = 6;
}
}
dp[i] += dp[i - 1] * 9;
} else if (cur == '0') {
if (pre == '*') {
if (i >= 2) {
dp[i] = dp[i - 2] * 2;
} else {
dp[i] = 2;
}
} else if (pre <= '2') {
if (i >= 2) {
dp[i] = dp[i - 2];
} else {
dp[i] = 1;
}
} else {
return 0;
}
} else {
if (pre == '0') {
dp[i] = dp[i - 1];
} else if (pre == '1') {
if (i >= 2) {
dp[i] = dp[i - 1] + dp[i - 2];
} else {
dp[i] = dp[i - 1] + 1;
}
} else if (pre == '2') {
if (cur <= '6') {
if (i >= 2) {
dp[i] = dp[i - 1] + dp[i - 2];
} else {
dp[i] = dp[i - 1] + 1;
}
} else {
dp[i] = dp[i - 1];
}
} else if (pre == '*') {
if (cur <= '6') {
if (i >= 2) {
dp[i] = dp[i - 1] + dp[i - 2] * 2;
} else {
dp[i] = dp[i - 1] + 2;
}
} else {
if (i >= 2) {
dp[i] = dp[i - 1] + dp[i - 2];
} else {
dp[i] = dp[i - 1] + 1;
}
}
} else {
dp[i] = dp[i - 1];
}
}
dp[i]%=MOD;
}
return (int) dp[s.length() - 1];
}
- 时间复杂度O(n)
- 空间复杂度O(n)
思路二:滚动数组+动态规划
- 根据转移方程可以很容易的发现可以使用滚动数组进行优化
- 用一下三叶大佬的代码!
class Solution {
int mod = (int)1e9+7;
public int numDecodings(String s) {
int n = s.length() + 1;
long[] f = new long[3];
f[0] = 1;
f[1] = s.charAt(0) == '*' ? 9 : (s.charAt(0) != '0' ? 1 : 0);
for (int i = 2; i < n; i++) {
char c = s.charAt(i - 1), prev = s.charAt(i - 2);
int p1 = (i - 1) % 3, p2 = (i - 2) % 3;
long cnt = 0;
if (c == '*') {
// cs[i] 单独作为一个 item
cnt += f[p1] * 9;
// cs[i] 与前一个字符共同作为一个 item
if (prev == '*') {
cnt += f[p2] * 15;
} else {
int u = (int)(prev - '0');
if (u == 1) cnt += f[p2] * 9;
else if (u == 2) cnt += f[p2] * 6;
}
} else {
int t = (int)(c - '0');
if (prev == '*') {
if (t == 0) {
cnt += f[p2]* 2;
} else {
// cs[i] 单独作为一个 item
cnt += f[p1];
// cs[i] 与前一个字符共同作为一个 item
if (t <= 6) cnt += f[p2] * 2;
else cnt += f[p2];
}
} else {
int u = (int)(prev - '0');
if (t == 0) {
if (u == 1 || u == 2) cnt += f[p2];
} else {
// cs[i] 单独作为一个 item
cnt += f[p1];
// cs[i] 与前一个字符共同作为一个 item
if (u == 1) cnt += f[p2];
else if (u == 2 && t <= 6) cnt += f[p2];
}
}
}
f[i % 3] = cnt % mod;
}
return (int)(f[(n - 1) % 3]);
}
}
- 时间复杂度O(n)
- 空间复杂度O(1)