算法重新出发:2.动态规划
- leetcode 91. 解码方法
class Solution {
public:
int numDecodings(string s) {
if(s[0]=='0') return 0;
int dp[s.size()+1];
dp[0]=1;
dp[1]=1;
for (int i = 1; i < s.size(); i++) {
if (s[i] == '0') {
if (s[i-1] == '1' || s[i-1] == '2') dp[i+1] = dp[i-1];
else return 0;
}
else {
if (s[i-1]=='1' || (s[i-1]=='2' && s[i]>='1' && s[i]<='6')) {
dp[i+1] = dp[i]+dp[i-1];
}
else dp[i+1] = dp[i];
}
}
return dp[s.size()];
}
};
动态规划能解决的问题是多阶段决策问题
如果一类活动过程可以分为若干个互相联系的阶段,在每一个阶段都需作出决策,一个阶段的决策确定以后,会影响到下一个阶段的决策,从而确定活动路线,则称它为多阶段决策问题。各个阶段的决策构成一个决策序列,称为一个策略。每一个阶段都有若干个决策可供选择,因而就有许多策略供我们选取。
如今天的leetcode 91. 解码方法
11106可以分解为如上两种消息,10定死是由于0只能和前面的1或2组合,6无法再组合
if (s[i] == '0') {
if (s[i-1] == '1' || s[i-1] == '2') dp[i+1] = dp[i-1];
else return 0;
}
当当前数为0时,0前的数若是1或2,则无法和别的数组合,0所对应的方案数和0的前两个数对应的方案数是一样的;0前的数若是0或3-9,则根本不可能组合,直接返回0
if (s[i-1]=='1' || (s[i-1]=='2' && s[i]>='1' && s[i]<='6')) dp[i+1] = dp[i]+dp[i-1];
else dp[i+1] = dp[i];
当当前数不为0时,若前一个数为1,则肯定能组合;或者当当前数为0-6时,若前一个数为2也能组合,对应的方案数为前一个数对应方案数个数加前两个数对应的方案数;否则则不能组合,对应的方案数为前一个数对应的方案数,因为自成一个,如11106中的6
上面这些代码就是找出来的状态转移方程,可以看出是解决了一个个小问题最后得到到最后的方案数的。能用动态规划解决的问题是上面说的决策问题被重复调用得出的,像226中的22,只有组合和不组合两种选择,每次都会影响到后续路径,若22组合则6只能单个,22不组合则6可以选择组合或不组合,而若是226后面还有数字,也会被6是否组合影响,但是后续的方案数和前面的组合有关却又可以通过状态转移方程直接调用前面的方案数而不是从头决定是否组合,这就是动态规划所带来的好处。
动态规划步骤:
- 创建一个一维数组或者二维数组,保存每一个子问题的结果,具体视题目而定(一维数组可以运用滚动数组的方式来解决,即一位数组中的值不停的变化)
- 设置数组边界值,如上题中的`dp[0]=1; dp[1]=1;
- 找出状态转移方程,即每个状态跟上一个状态的关系,根据方程写出代码
- 返回值,一般是数组的最后一个或者二维数组的最右下角
上述代码因为从循环1开始,又有涉及到前两个数的状态转移方程,所以我把dp的下标全都加了1,若是用滚动数组的方式解决,则代码如下:
class Solution {
public:
int numDecodings(string s) {
if(s[0]=='0') return 0;
int a1 = 1;
int a2 = 1;
for (int i = 1; i < s.size(); i++) {
if (s[i] == '0') {
if (s[i-1] == '1' || s[i-1] == '2') a2 = a1;
else return 0;
}
else {
if (s[i-1]=='1' || (s[i-1]=='2' && s[i]>='1' && s[i]<='6')) {
a2 = a2+a1;
}
}
a1 = tem;
}
return a2;
}
};