题目概述:
题解
这个题其实建立动态规划的指标比较容易,很容易想到就是以第i个字符为结尾的解码方法数,但是难想的是状态转移方程,这里定义f(i)f(i)f(i)为以第i个字符结尾时的解码方法数。
注意到一个新的字符加入前面的串后,有两种解码方式:一种是自己作为一个单独的字符解析,然后前面保留原来的解析方式,这种解码的要求是字符s[i−1]s[i - 1]s[i−1]可以单独解析,也就是说s[i−1]!=′0′s[i-1] != '0's[i−1]!=′0′,这种情况下,解码方法数就等于f(i−1)f(i -1)f(i−1);
另一种解码方法是以自己和前面一个字符为一个整体解码,这样解码的方法数等于以第i - 2个字符为结尾时串的解码数f(i−2)f(i - 2)f(i−2),这里要和前一个字符整体解码,因此首先要求前一个字符不能是'0'
,因为题目规则规定了'06'
这种解析方法是不合法的,另外,如果要这两个字符是一个有效合法解析,还要求这样两个字符组成的数字不大于26,超出26的话就不能解析为任何一个英文字母了。
如果这两种方法都不能解析,说明以该字符结尾的串没有解析方法,由于vector容器在创建时,值默认初始化为0,所以除了这两种情况以外就不需要管了,直接就是0种方法。
代码:
class Solution {
public:
int numDecodings(string s)
{
int size = s.size();
//f(i)表示以第i个字符结尾的串的解析方法数
vector<int> f(size + 1);//第一个f[0]对应空字符串
//f(0) = 1表明空字符可以解析成一个''只不过不能组合而已
f[0] = 1;
for (int i = 1; i <= size; ++i)
{
//第一种情况 最后一个字符自己解析 不和前面的字符结合
//能解析的前提是它不等于'0'
//这种情况的方法数等于(1,i-1)字符的解析数目
if (s[i - 1] != '0')
{
f[i] += f[i - 1];
}
//第二种情况 用倒数两个字符解析
//能解析的前提是倒数第二个字符不等'0'
//这种方法数等于(i, i - 2)字符解析的方法数
if (i > 1 && s[i - 2] != '0' && (s[i - 2] - '0') * 10 + (s[i - 1] - '0') <= 26)
{
f[i] += f[i - 2];
}
}
return f[size];
}
};
注意到状态仅和f(i−1)f(i - 1)f(i−1)与f(i−2)f(i - 2)f(i−2)有关,因此可以优化时间复杂度,用三个变量分别维护第i个字符的f(i),f(i−1),f(i−2)f(i),f(i - 1),f(i - 2)f(i),f(i−1),f(i−2),代码如下:
class Solution {
public:
int numDecodings(string s)
{
//a = f(i) b = f(i - 1) c = f(i - 2)
int a = 0;//尚未计算
int b = 1;
int c = 0;//尚不存在
int size = s.size();
for (int i = 1; i <= size; ++i)
{
//先把 a清空 消上一轮计算的存留
a = 0;
//第一种情况 最后一个字符自己解析 只要它不等于'0'就可以自己解析
if (s[i - 1] != '0')
{
a += b;
}
if (i > 1 && s[i - 2] != '0' && (s[i - 2] - '0') * 10 + (s[i - 1] - '0') <= 26)
{
a += c;
}
c = b;
b = a;
//其他情况都解析不出来字符,就不用管了 走一步a置成0 然后更新一下c和b就行了
}
return a;
}
};
时间复杂度:O(n)O(n)O(n)
空间复杂度:O(1)O(1)O(1)