【DP】LeetCode 91. 解码方法
题目链接
思路
分析动态规划题目的时候只需要考虑最后一个阶段,因为所有的阶段转化都是相同的,考虑最后一个阶段容易发现规律
在数组的动态规划问题中,一般 dp[i]
都是表示以 nums
以前 i 个元素组成(即 nums[i - 1]
)的状态;dp[i][j]
分别表示以 nums1
前 i 个元素(即 nums1[i - 1]
)组成和以 nums2
前 j 个元素(即 nums2[j - 1]
)组成的状态,以此类推
字符串也是个数组,是字符数组
表示状态
状态表示就是靠猜,但是会有猜的套路,一般都是通过最终结果和数组数量来猜
这道题和【DP】LeetCode 剑指 Offer 46. 把数字翻译成字符串非常像,但是要复杂一些,原因是46题中0也能映射为一个字符,而本题中的0却是无效的
状态的定义还是相似的,定义 \(dp[i]\) 表示 \(s[i]\) 前 i 个字符的解码总数
找状态转移方程
思考的方向是:大问题的最优解怎么由小问题的最优解得到
分解思路可以参考【DP】LeetCode 剑指 Offer 46. 把数字翻译成字符串
但是因为本题中要考虑0转换问题,而且前导0是无效的(即"06"不能视为"6"),所以我们不能通过 substring
截取再转化为 value
的方式来做(因为这种方式会视为"06"与"6"等价)
边界处理
dp[0] = 1;
dp[1] = s.charAt(0) == '0' ? 0 : 1;
空间优化
因为 \(dp[i]\) 只和 \(dp[i - 1]\) 还有 \(dp[i - 2]\) 有关,所以可以用两个单变量 \(preDp1\) 和 \(preDp2\) 分别代表 \(dp[i - 1]\) 还有 \(dp[i - 2]\),在遍历过程中滚动更新
代码
在判断 \(s[i - 2] = 1 \ or \ (s[i - 2] = 2 \ and \ s[i - 1] \in [0, 6])\) 时不能使用转化为数字的方法,因为这样会将 "06" 转换成 "6" 从而得不出正确结果
dp
数组版
class Solution {
public int numDecodings(String s) {
int n = s.length();
if(n < 1){
return 0;
}
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = s.charAt(0) == '0' ? 0 : 1;
for(int i = 2; i <= n; i++){
int c = s.charAt(i - 1);
int pre = s.charAt(i - 2);
// s[i - 1] 能单独成字母
if('1' <= c && c <= '9'){
dp[i] += dp[i - 1];
}
// s[i - 1] 和 s[i - 2] 能组合成字母
if(pre == '1' || (pre == '2' && c <= '6')){
dp[i] += dp[i - 2];
}
}
return dp[n];
}
}
空间优化版
class Solution {
public int numDecodings(String s) {
int n = s.length();
if(n < 1){
return 0;
}
int preDp1 = 1;
int preDp2 = s.charAt(0) == '0' ? 0 : 1;
int current = 0;
for(int i = 2; i <= n; i++){
int c = s.charAt(i - 1);
int pre = s.charAt(i - 2);
// s[i - 1] 能单独成字母
if('1' <= c && c <= '9'){
current += preDp2;
}
// s[i - 1] 和 s[i - 2] 能组合成字母
if(pre == '1' || (pre == '2' && c <= '6')){
current += preDp1;
}
preDp1 = preDp2;
preDp2 = current;
current = 0;
}
return preDp2;
}
}