法1:动态规划
想法:
- 用数组dp保存以当前数字字符结尾的串的解码方法总数
- 对于当前数字字符s[i],若s[i - 1]s[i]衔接而成的数字可解码(即 >=1 且 <= 26),则dp[i] = dp[i - 2] + dp[i - 1](dp[i - 2]表示s[i - 1]s[i]一起解码,再加上s[i - 1]之前的字符的解码数;dp[i - 1]表示s[i]单独解码,再加上s[i]之前的字符的解码数)
- 对于当前数字字符s[i],若s[i - 1]s[i]衔接而成的数字不可解码,则s[i]单独解码:dp[i] = dp[i - 1]
- 需特殊考虑s[i] == '0’的问题 ('0’只可与它的前一位衔接解码,否则无法解码)
- 若s[0] == ‘0’,则无法解码,return 0
- 对于s[i],若s[i] == ‘0’,需判断s[i - 1]s[i]是否可解码:可解码则dp[i] = dp[i - 2];不可解码,则return 0
- 对于s[i],若s[i] != ‘0’,则需判断s[i - 1]是否为’0’:若为’0’则s[i]只能单独解码,dp[i] = dp[i - 1];若不为’0’则可考虑s[i - 1]s[i]组合解码(如2、3中所述)
/**
* @param {string} s
* @return {number}
*/
var numDecodings = function(s) {
if(s[0] == '0') { // 0在首位,则不可能解码成功
return 0;
}
if(s.length == 1) { // 只有一位,且第一位不是0
return 1;
}
var dp = [];
var i = 0;
// 先对dp[0]和dp[1]初始化
dp[0] = 1;
if(s[1] == '0') { // s[1]不可单独解码
if(s[0] == '1' || s[0] == '2') { // 只有'10' '20'可解码
dp[1] = 1;
}
else {
return 0;
}
}
else {
if(Number(s[0] + s[1]) >= 1 && Number(s[0] + s[1]) <= 26) {
// s[0]s[1]可解码
dp[1] = 2; // 1. s[0]和s[1]分开解码;2. s[0]s[1]一起解码
}
else {
dp[1] = 1; // s[0]和s[1]分开解码
}
}
for(i = 2; i < s.length; i++) {
if(s[i] == '0') { // 出现0,则0必须和s[i - 1]能够一起解码
if(s[i - 1] == '1' || s[i - 1] == '2') {
dp[i] = dp[i - 2];
}
else {
return 0;
}
}
else { // 不为0
if(s[i - 1] == '0') { // 则s[i]只能单独解码
dp[i] = dp[i - 1];
}
else { // s[i]可能可以与s[i - 1]解码
if(Number(s[i - 1] + s[i]) >= 1 && Number(s[i - 1] + s[i]) <= 26) {
// s[i - 1]s[i]可解码
dp[i] = dp[i - 1] + dp[i - 2];
}
else {
dp[i] = dp[i - 1];
}
}
}
}
return dp[s.length - 1];
};
优化1
想法:
- dp[i]只与dp[i - 1]、dp[i - 2]有关,故:pre1保存dp[i - 1],pre2保存dp[i - 2],cur保存dp[i]
/**
* @param {string} s
* @return {number}
*/
var numDecodings = function(s) {
if(s[0] == '0') { // 0在首位,则不可能解码成功
return 0;
}
if(s.length == 1) { // 只有一位,且第一位不是0
return 1;
}
var pre1 = 0, pre2 = 0, cur = 0;
var i = 0;
// 先对dp[0]和dp[1]初始化
pre2 = 1;
if(s[1] == '0') { // s[1]不可单独解码
if(s[0] == '1' || s[0] == '2') { // 只有'10' '20'可解码
pre1 = 1;
}
else {
return 0;
}
}
else {
if(Number(s[0] + s[1]) >= 1 && Number(s[0] + s[1]) <= 26) {
// s[0]s[1]可解码
pre1 = 2; // 1. s[0]和s[1]分开解码;2. s[0]s[1]一起解码
}
else {
pre1 = 1; // s[0]和s[1]分开解码
}
}
for(i = 2; i < s.length; i++) {
if(s[i] == '0') { // 出现0,则0必须和s[i - 1]能够一起解码
if(s[i - 1] == '1' || s[i - 1] == '2') {
cur = pre2;
}
else {
return 0;
}
}
else { // 不为0
if(s[i - 1] == '0') { // 则s[i]只能单独解码
cur = pre1;
}
else { // s[i]可能可以与s[i - 1]解码
if(Number(s[i - 1] + s[i]) >= 1 && Number(s[i - 1] + s[i]) <= 26) { // s[i - 1]s[i]可解码
cur = pre1 + pre2;
}
else {
cur = pre1;
}
}
}
pre2 = pre1;
pre1 = cur;
}
return pre1;
};
优化2
看了题解,换了一种分类方式
想法:
- 若s[i] == ‘0’,则只有当s[i - 1] 为’1’或’2’时,s[i - 1]s[i]可共同解码:cur = pre2
- 若s[i] != ‘0’,则:
- 若s[i - 1] == ‘1’,则s[i - 1]和s[i]可分开解码,也可一起解码:cur = pre1 + pre2
- 若s[i - 1] == ‘2’ && s[i] >= ‘1’ && s[i] <= ‘6’,则s[i - 1]和s[i]可分开解码,也可一起解码:cur = pre1 + pre2
- 以上两种情况都不满足,则只有s[i]单独解码:cur = pre1
注:pre1为以s[i - 1]字符结尾的串的解码方法总数;pre2为以s[i - 2]字符结尾的串的解码方法总数;cur为以s[i]字符结尾的串的解码方法总数
/**
* @param {string} s
* @return {number}
*/
var numDecodings = function(s) {
if(s[0] == '0') { // 0在首位,则不可能解码成功
return 0;
}
var pre1 = 0, pre2 = 0, cur = 0;
var i = 0;
pre1 = pre2 = 1;
for(i = 1; i < s.length; i++) { // 从第二位数字开始
if(s[i] == '0') { // 出现0,则0必须和s[i - 1]能够一起解码
if(s[i - 1] == '1' || s[i - 1] == '2') {
cur = pre2;
}
else {
return 0;
}
}
else { // 不为0
if(s[i - 1] == '1') {
cur = pre1 + pre2;
}
else if(s[i - 1] == '2' && (s[i] >= '1' && s[i] <= '6')){
cur = pre1 + pre2;
}
else {
cur = pre1;
}
}
pre2 = pre1;
pre1 = cur;
}
return pre1;
};