算法那些事专栏说明:这是一个记录刷题日常的专栏,每个文章标题前都会写明这道题使用的算法。专栏每日计划至少更新1道题目,在这立下Flag
个人主页:Jammingpro
专栏链接:算法那些事
每日学习一点点,技术累计看得见
一条包含字母 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” 在映射中并不等价。
给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。
题目数据保证答案肯定是一个 32 位 的整数。
示例 1:
输入:s = “12”
输出:2
解释:它可以解码为 “AB”(1 2)或者 “L”(12)。
示例 2:
输入:s = “226”
输出:3
解释:它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。
示例 3:
输入:s = “06”
输出:0
解释:“06” 无法映射到 “F” ,因为存在前导零(“6” 和 “06” 并不等价)。
1 <= s.length <= 100
s 只包含数字,并且可能包含前导零。
首先,我们通过上面的解码规则,对"123"进行解码。“1”、“2”、“3"均能够单独解码,这是一种解码方法;将"1"和"2"组合解码为"12”,其范围在1到26之间,故解码成功,因此"12"、“3"是一种解码方法;将"1"单独解码,“2"和"3"组合解码为"23”,其范围在1到26之间,故也解码成功,因此"1”、"23"也是一种解码方法。所以"123"共有3种解码方法。
再看一个示例:对"1032"进行解码。“1”、“0”、“3”、"2"无法分开单独解码,因为"0"不在1到26范围内,因此,"0"需要与"1"或"3"组合解码。“1"和"0"组合解码为"10”,在1到26之间;“0"和"3"组合解码为"03”,因为其包含前导0,故不满足题目要求。因此,"0"必须与"1"组合解码。“3"和"2"可以分开解码,但无法组合解码,因为其组合为"32”,超出26,故解码失败。这个字符串只有一种解码方法,即10
、3
、2
。
由上面分析可知,遇到①组合数有前导0、②构成数不在1到26之间,将会导致解码失败。除此之外,出现连续两个0,即"00",也会解码失败。
我们可以s[i]和s[i-1]是否为0做分类分析:
若s[i]==‘0’,则我们再判断s[i-1]是否为’0’,如果s[i]==0&&s[i-1]==0,则出现连续两个0,这种排列方式无法解码,直接返回0;如果s[i]==0&&s[i-1]!=0,即s[i]无法单独解码,需要与s[i-1]组合在一起才能解码,如果s[i]与s[i-1]组合数<=26,则dp[i]=dp[i-2],否则return 0(即两个字符无法分开解码,也无法合在一起解码)。
ps:为什么这里dp[i]=dp[i-2]呢?dp[i-2]中保存的是从0号字符到i-2号字符的解码方法。因为上述情况只能组合解码,因此s[i]与s[i-1]需组合解码到一起,所以dp[i]的解码方法与dp[i-2]的解码方法相同。
若s[i]!=‘0’,则我们再判断s[i-1]是否为’0’,如果s[i]!=0&&s[i-1]==0,则两个字符无法一起解码,需要s[i-1]与s[i-2]各自解码,因此dp[i]=dp[i-1](此时出现前导0的情况,前导0无法和s[i]组合,但可以与s[i-2]组合;如果无法与s[i-2]组合,则上一次迭代已经return 0了);如果s[i]!=‘0’&&s[i-1]!=‘0’,若两个数字组合数<=26,则dp[i]=dp[i-1]+dp[i-2],若组合数>26,则dp[i]=dp[i-1]。
ps:这里怎么有dp[i]=dp[i-1]+dp[i-2]?因为s[i]能和s[i-1]组合解码,其组合解码后,s[0-i]的解码方法数与dp[i-2]相同;s[i]和s[i-1]分开解码,则s[0-i]阶解码方法数与dp[i-1]相同,故得到上述式子。
经过上面的分析,我们可以得到如下代码↓↓↓
class Solution {
public:
int numDecodings(string s) {
if(s[0] == '0') return 0;
int n = s.size();
if(n == 1) return 1;
vector<int>dp(n);
dp[0] = 1;
if(s[1] == '0')
{
if((s[0] - '0') * 10 + (s[1] - '0') <= 26)
dp[1] = 1;
else
return 0;
}
else
{
if((s[0] - '0') * 10 + (s[1] - '0') <= 26)
dp[1] = 2;
else
dp[1] = 1;
}
for(int i = 2; i < n; i++)
{
if(s[i] == '0' && s[i - 1] == '0') return 0;
if(s[i] == '0')
{
if((s[i - 1] - '0') * 10 + (s[i] - '0') <= 26)
{
if(s[i - 1] == '0')
dp[i] = dp[i - 1];
else
dp[i] = dp[i - 2];
}
else
return 0;
}
else
{
if((s[i - 1] - '0') * 10 + (s[i] - '0') <= 26)
{
if(s[i - 1] == '0')
dp[i] = dp[i - 1];
else
dp[i] = dp[i - 1] + dp[i - 2];
}
else
dp[i] = dp[i - 1];
}
}
return dp[n - 1];
}
};
这个代码看起来非常繁琐,嵌套了需要分支语句,可阅读性较差。下面我们对这个代码做一些优化↓↓↓
class Solution {
public:
int numDecodings(string s) {
int n = s.size();
vector<int>dp(n + 1);
dp[0] = 1;//这里没有实质性含义,因为s[0]与s[1]组合数若满足10-26,需要+dp[0],故初始化为1
dp[1] = s[0] != '0';
for(int i = 2; i <= n; i++)
{
int t = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');//计算组合数
dp[i] = s[i - 1] != '0' ? dp[i - 1] : 0;//当前字符不为0,则可以单独解码,则dp[i]=dp[i-1]
dp[i] += t >=10 && t <= 26 ? dp[i - 2] : 0;//组合数在10-26之内,则+dp[i-2]
}
return dp[n];
}
};
优化代码中的dp下标与s下标差1,代码思路与前一代码类似,但更为巧妙。
本文存在不足,欢迎留言或私信批评、指正。希望我的解决方法能够对你有所帮助~~
今日打卡完成,点亮小星星☆→★