91.解码方法
dp问题描述
求解码方法总数
我们在学习Huffman编码时都知道,编码过程中出现的前缀覆盖问题会导致编码出现歧义,所以我们通过构建哈夫曼树的方法来求出最短且没有歧义的前缀编码。那么这个题目就是给你一个存在前缀覆盖问题的编码方案,让你求出指定消息的所有解码方案
到这里问题其实我们已经明白的差不多了,但是他怎么能和动态规划沾上边儿呢?这个题目的状态表示是什么?状态转移方程应该怎么写?
确定本题的状态表示
dp[i]表示的是s[0]~s[i]所构成的子字符串的解码方案的总数
确定本题的状态转移方程
假如说我们现在要确定dp[i],根据我们的经验,dp[i]绝大部分情况下都能用dp[i-1]和dp[i-2]来表示,即dp[i] = f(dp[i-1],dp[i-2] )
那现在我们就要去想如何用dp[i-1]和dp[i-2]去表示dp[i]
对于本题来说,假如s[i]单独解码以及s[i-1]和s[i]联合解码,都可以对应一个字母映射,那么dp[i]=dp[i-1]+dp[i-2]
可是由于并不是任意两个数字结合都能映射到一个字母(值大于等于27的两位数就不行),也不是任意一个数字都能映射到一个字母(0不行),因此我们在从左到右遍历的时候就要考虑下面的情况
s[i]=='0'
时应该怎么处理?s[i-1]=='0'
时应该怎么处理?- s[i-1]与s[i]组合成的两位数大于26该怎么处理?
其实最折磨的就是s[i]=='0'
的处理,s[i-1]的情况不同,对应的dp[i]的递推式也不相同。有可能解不出来,比如s[0]='0'
,连续的两个零,或者80,也有可能可以解出来,比如20
你要综合考虑,算出每一种情况下dp数组的递推表达式
填表求值
根据初始条件和状态转移方程,确定填表顺序,进而逐步填满dp表,最终返回题目要的结果
代码实现
我的代码
class Solution {
public:int numDecodings(string s) {int n=s.size();if(n==0||s[0]=='0') return 0;else if(n==1) return 1;vector<int> dp(n+1);dp[0]=1;dp[1]=1;for(int i=1;i<n;i++){int tmp=(s[i-1]-'0')*10+s[i]-'0';if(tmp==0||(s[i]=='0'&&tmp>=30)) return 0;if(tmp>=27) dp[i+1]=dp[i];else if(s[i]=='0')dp[i+1]=dp[i-1];else if(s[i-1]=='0') dp[i+1]=dp[i];else dp[i+1]=dp[i]+dp[i-1];// cout << "s[i]="<< s[i]<<" dp["<<i<<"]="<<dp[i]<< endl;}return dp[n];}
};
从我们上面的代码可以看出,dp[i+1]理想情况下应该等于dp[i]+dp[i-1],遇到特殊情况可能不加dp[i],也可能不加dp[i-1],但这个题目的这种情况实在是太复杂了。所以我们按照正难则反的思想,只讨论什么情况下dp[i+1]应该+=dp[i],什么情况下dp[i+1]应该+=dp[i-1],满足条件我就加,不满足条件我就不加。这样可能会好想一点。
看了网上的优质解法,感觉和我的思路也不太一样。但是今天实在是没有精力继续研究了,先把它放这儿吧,以后有空。再来仔细看看
int numDecodings(string s) {int n = s.size();vector<int> dp(n);dp[0] = s[0] != '0';if (n == 1) return dp[0];if (s[0] != '0' && s[1] != '0') dp[1] += 1;int t = (s[0] - '0') * 10 + s[1] - '0';if (t >= 10 && t <= 26) dp[1] += 1;for (int i = 2; i < n; i++) {if (s[i] != '0') dp[i] += dp[i - 1];int t = (s[i - 1] - '0') * 10 + s[i] - '0';if (t >= 10 && t <= 26) dp[i] += dp[i - 2];}return dp[n - 1];
}
进一步优化版本
class Solution {
public:int numDecodings(string s) {// 优化int n = s.size();vector<int> dp(n + 1);dp[0] = 1; // 保证后面的填表是正确的dp[1] = s[ - 1] != '0';for(int i = 2; i <= n; i++){if(s[i - 1] != '0') dp[i] += dp[i - 1]; // 处理单独编码的情况int t = (s[i - 2] - '0') * 10 + s[i - 1] - '0'; // 第二种情况所对应的数if(t >= 10 && t <= 26) dp[i] += dp[i - 2];}return dp[n];}
};