赞
踩
好吧我承认我又懒了,KMP算法是一个比较复杂的算法,如果用动态规划来解决更不是一句两句话说得清楚的,所以周四没课那天更新KMP算法动态规划解决的文章。今天聊点不复杂且和前文有直接关系的——动态规划之子序列问题解题模板
我们知道,子序列问题比子串问题要难,因为子串问题是连续的,而子序列问题是不连续的,我们用暴力法都不一定可以解决。一般题目不会让我求最短子序列,因为最短子序列肯定是一个字符啊,没有比它更短的了。所以一般都是最长子序列问题。那最长子序列问题暴力法无法解决,而且还有最值要求,我们很自然地想到了动态规划算法,时间复杂度为O(n^2)。(也有优化的方法,比如二分法优化单调最长子序列,时间复杂度为O(nlogn))
解决这种子序列问题一般我们有两种思路,这两种思路都是针对dp table的定义的
1.使用1维的dp table
int n = array.size();
int dp[n] = new int[n];
for(int i = 1;i < n;i++)
{
for(int j = 0;j < i;i++)
dp[i] = 最值(dp[i],dp[j]+.....);
}
2.使用2维的dp table
int n = array.size();
int dp[n][n] = new int[n][n];
for(int i = 0;i < n;i++)
{
for(int j = 0;j < n;j++)
{
if(array[i] == array[j])
dp[i][j] = dp[i][j]+....;
else
dp[i][j] = 最值(....)
}
}
我们今天要聊的这个最长回文子序列问题就是这么一个模板,但是这种模板还需要分成不同的情况:
2.1涉及两个字符串/数组(最长公共子序列)
dp[i][j]表示arr1[0…i]和arr2[0…j]的最长公共子序列长度。
2.2只涉及一个字符串/数组(比如最长回文子序列)
dp数组含义如下:在子数组array[i…j]中,我们要求的最长回文子序列长度。
OK,介绍完了基本的模板我们开始解决实际问题——最长回文子序列
问题介绍很简单,我们只需要找出字符串中最长的回文子序列即可,而解题模板就是刚刚介绍的2维dp table单字符串情形
状态转移方程
我们通过这个图来理解一下状态转移方程,找状态转移方程需要归纳思维,说白了就是如何从已知的结果推出未知的部分。
所以我们来思考一下状态转移方程应该如何写呢?
1.如果s[i]==s[j]
如果当前的s[j]与s[j]相等了,那么dp[i][j]应该通过上一个可能构成回文子序列的序列推导出,s[i…j]之前一个可能构成回文子序列的序列是什么呢?
既然可以构成回文子序列,那么一定是s[i]=s[j],s[i+1]=s[j-1]…,所以上一个回文子序列的长度应该为dp[i+1][j-1].
所以状态转移方程为dp[i][j] = dp[i+1][j-1]+2
2.如果s[i]!=s[j]
s[i]!=s[j]的时候我们思路是一样的,找出上一个可能是回文子序列的序列。已知s[i]!=s[j],则上一段序列我们应该看s[i…j-1]这一段或者s[i+1…j]这一段。
所以状态转移方程为dp[i][j] = max(dp[i+1][j],dp[i][j-1])
base case
明确了状态转移方程其实问题就解决了一大半了,下面就是base case了。
我们要时刻记住,我们的研究对象是一个字符串,对于字符串来说会有什么极端情况(base case)呢?那就是当i>=j的时候。
如果i==j
那么我们就是要找s[i…i]的最长回文子序列,答案可想而知,就是1
如果i>j
那么我们就是要找s[i…j]的最长回文子序列,根本不存在这样的序列不是吗?所以答案是0.
动态规划问题,明确了状态转移方程,明确了base case,知道了如何做选择,那么即使你没写代码,这个问题你其实也解决了。但是在这里我们还是给出代码。
我们可以发现dp[i][j]的来源一共有三种可能:dp[i+1][j-1],dp[i+1][j],dp[i][j-1]
所以我们在二维dp table上可以画出它的方向:
这里的0和1,是我们的base case。可以看出,我们只有明确了左,左下,和下才能确定当前dp值,所以由图可知,正向遍历并不能得到答案,所以我们可以斜着遍历或者反向遍历:
这里我选择反向遍历,即上图图二。
int longestPalindromeSubseq(string s) { int n = s.length(); //二维dp数组全部初始化为0 vector<vector<int>> dp(n,vector<int>()n,0); for(int i = 0;i < n;i++) dp[i][i] = 1; //反着遍历数组 for(int i = n-1;i >= 0;i++) { for(int j = i+1;j < n;j++) { if(s[i] == s[j]) { dp[i][j] = dp[i+1][j-1] + 2; } else { dp[i][j] = max(dp[i+1][j],dp[i][j+1]); } } } //整个s的最长回文子串长度 return dp[0][n-1]; }
OK,问题到这里就彻底解决了,希望今天聊的话题可以让大家对子序列问题有一个更清晰的认识,也可以利用模板解决类似问题。
还有!我保证周四一定跳出comfort zone更新动态规划版KMP算法。周二我们聊编辑距离问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。