携手创作,共同成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
解码方法
一条包含字母 A-Z 的消息通过以下映射进行了 编码 : 'A' -> "1" 'B' -> "2" ... 'Z' -> "26"。 给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。
分析
- 通过题目描述,有点像爬楼梯,要么一步,要么两步,对应的是字符串1位或者2位1-26对应A-Z。
- 对于这种确定总数的题目可以使用动态规划。
动态规划
- 首先把整个问题分解成小问题,这个小问题即1位字符,那解码方法数要么是1要么是0(字符是‘0’的时候不能解码),解码方法的总数状态树从叶子节点向根节点叠加。
- 确定状态:dp[i]表示s的第i个位置解码方法数
- 初始化:当s为0的时候不能解码,其他情况下dp[s.length-1]=1
- 状态转移:位置i的解码方法有两种,即i后1位字符(0-9)或者2位字符(10-26),即dp[i] = dp[i+1]+dp[i+2]
代码
/**
* @param {string} s
* @return {number}
*/
var numDecodings = function (s) {
const dp = [];
dp[s.length] = 1;
for (let i = s.length - 1; i >= 0; i--) {
if (s[i] === '0') {
dp[i] = 0;
}
else {
dp[i] = dp[i + 1]
}
if (i + 2 <= s.length && (s[i] === '1' || s[i] === '2' && '0123456'.includes(s[i + 1]))) {
dp[i] += dp[i + 2]
}
}
return dp[0];
};
另一种动态规划
- 确定状态:dp[i]是s[i]为结尾的子字符串的解码方法
- 初始化:当字符串为0的时候不能解码,其他情况下dp[0] = 1
- 状态转移:当s[i]不是0的时候,dp[i]=dp[i-1],如果前两位能解码组成10-26,dp[i]=dp[i-1]+dp[i-2]
- 需要注意i-1和i-2下标不溢出
代码
var numDecodings = function(s) {
const n = s.length;
const f = new Array(n + 1).fill(0);
f[0] = 1;
for (let i = 1; i <= n; ++i) {
if (s[i - 1] !== '0') {
f[i] += f[i - 1];
}
if (i > 1 && s[i - 2] != '0' && ((s[i - 2] - '0') * 10 + (s[i - 1] - '0') <= 26)) {
f[i] += f[i - 2];
}
}
return f[n];
};
总结
- 虽然都是动态规划,但是对于状态的定义不一样,状态转移方程和最后返回的表达式也不同,边界函数也不一样,但是很多时候在解动态规划的题目的时候,都忽略了定义状态的重要性,有时候换个定义,理解起来就不一样了
- 我们习惯根据题意从上向下画状态树,分析状态也都是从上往下,有时候从下往上反过来分析也提供了另一种思路
- 今天也是有收获的一天