A message containing letters from A-Z
is being encoded to numbers using the following mapping:
'A' -> 1
'B' -> 2
...
'Z' -> 26
Given an encoded message containing digits, determine the total number of ways to decode it.
For example,
Given encoded message "12"
, it could be decoded as "AB"
(1 2) or "L"
(12).
The number of ways decoding "12"
is 2.
问题:对给定纯数字字符串解码,求解法的可能情况有多少种。
注:0 开头的字符串算作无效, 例如, "02" 无法解码。
也是一道蛮典型的 DP 问题。
总的解码个数,等于对第一个数字单独解码的情况,加上对前两个数字一起解码的情况。
利用 vector<int> v 记录中间计算结果。 v[i] 表示 s[i...n] 的解码个数。
vector<int> v; int decode(string s, int p){ if (v[p] != -) {
return v[p];
} if (s[] == '') {
v[p] = ;
return ;
} if (s.size() == ) {
v[p] = ;
return ;
} if (s.size() == ) {
int tmp = ;
if (s[] != '') {
tmp++;
} if (stoi(s) <= ) {
tmp++;
}
v[p] = tmp;
return tmp;
} int n1 = ;
if (s[] != '') {
n1 = decode(s.substr(), p+);
} int n2 = ;
if (stoi(s.substr(, )) <= ) {
n2 = decode(s.substr(), p+);
} int res = n1 + n2; v[p] = res;
return res;
} int numDecodings(string s) {
vector<int> tmp(s.size() , -); v = tmp; if(s.size() == ){
return ;
} int res = decode(s, ); return res;
}
这道题解了两次,第一次没有解出来,放在一边,做了其他 DP 题目后,第二次再做,觉得顺畅了许多。
第一次解的时候,思路是分治(Divide and conquer)。
分治主要有三个步骤:
分(Divide) :将原问题分割为类型相同的子问题。
治(Conquer) :分别解决各个子问题
整合(Combine) : 将各个子问题结果整合,得到原问题解。
算法: 将密码字符串从中间分割,分别求解两个子问题,并求解中间不分割的情况,整合三个结果,得到原问题的解。
但是,求解中间不分割情况处理起来比较复杂,就没有继续下去。
第二次解的时候,已经做了一些 DP 问题,思路就往 DP 方向想。
动态规划(Dynamic Programming) 的关键条件有两个:
重叠子问题(overlapping sub-problems) : 子问题和原问题是同类型,解法可重用。这点和分治的前两个步骤类似。
最优子结构(optimal substructure) :子问题的结果,可以比较直接地得到原问题的结果。
对于字符串问题,
若采用分治思路,左右子问题都必须求解,并且还要考虑中间不分割的情况,算法容易变得复杂。
若采用 DP 思路,从左往右一直扫过去,免去了分治中求整合的那一步骤,时间复杂度和分治应该差不多,不过算法实现更加简洁。