一条包含字母 A-Z 的消息通过以下方式进行了编码:
‘A’ -> 1
‘B’ -> 2
…
‘Z’ -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
示例 1:
输入: “12”
输出: 2
解释: 它可以解码为 “AB”(1 2)或者 “L”(12)。
示例 2:
输入: “226”
输出: 3
解释: 它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。
解析:本题初看很像之前爬楼梯之类。
首先尝试使用递归方法解决问题。
定义函数R,返回对应字符串的解码结果数量
举例12026
从末尾开始看
对于6,很明显结果为1
对于26,结果为R(26)=R(6)+R()=2
对于026,结果为0
对于2026,结果为R(2026)=R(026)+R(26) =2
对于12026,结果为R(12026)=R(2026)+R(026)=2
可以得出结论,如果当前字符串前两个数字之和小于26,结果为R(p+1)+R(p+2)
否则为R(p+1),字符串不能以0开头
尝试写出代码
class Solution {
public:
int numDecodings(string s) {
if(s.size()==0) return 0;
return recursive(s,0);
}
int recursive(string &s,int p){
if(s.size()==p) return 1; //终止条件
if(s[p]=='0') return 0; //没有0开头的数字
int ans1 = recursive(s,p+1);
int ans2 = (p<s.size()-1)?((((s[p]-'0')*10+(s[p+1]-'0'))<=26)?recursive(s,p+2):0):0;
return ans1+ans2;
}
};
问题已经解决,但是耗时极长,因为递归调用次数太多,相同的结果多次重复计算。
尝试减少重复计算次数。
将递归转化为迭代,定义一数组,长度与给定字符串相同,用来储存对应位置的解码结果数量,即上文中的函数R
从后往前遍历字符串,按位计算数量,最后数组的第一个元素即为答案
class Solution {
public:
int numDecodings(string s) {
if(s.size()==0) return 0;
int l = s.size();
int dp[l+1];
memset(dp,0,sizeof(dp));
dp[l]=1;
if(s[l-1]=='0') dp[l-1]=0;
else dp[l-1]=1;
for(int i=l-2;i>=0;--i){
if(s[i]=='0'){
dp[i]=0;
continue;
}
if(((s[i]-'0')*10+(s[i+1]-'0'))<=26){
dp[i]=dp[i+1]+dp[i+2];
}
else{
dp[i]=dp[i+1];
}
}
return dp[0];
}
};
将递归转换为迭代,时间利用率提升了无数倍。
观察整个过程。
对于某一个位置p,R§仅仅用到了R(p+1)与R(p+2),并且随着继续遍历,之前的R再也没有用到过。
因此可以将n个长度的数组转换为一个仅有三个元素的数组,分别储存R§、R(p+1)、R(p+2)
并且在每一次移动后,R§转换为新的R(p+1)、R(p+1)转换为新的R(p+2)
最后遍历完成后返回R§即可
class Solution {
public:
int numDecodings(string s){
if(s.size()==0||s[0]=='0') return 0;
if(s.size()==1) return 1;
int dp[3]{1,1,0};
for(int i=2;i<s.size()+1;++i){
if(s[i-1]=='0'){
if(s[i-2]=='1'||s[i-2]=='2')
dp[2] = dp[0];
else return 0;
}
else{
if(s[i-2]=='1'||(s[i-2]=='2'&&s[i-1]<'7'))
dp[2] = dp[1]+dp[0];
else
dp[2] = dp[1];
}
dp[0] = dp[1];
dp[1] = dp[2];
}
return dp[2];
}
};
时间复杂度O(n)
空间复杂度O(1)