最长公共子列问题-DP问题回顾
LCS问题的递推公式是:
C[i,j]->A的前i和元素和B的前j个元素的最大公共子列的长度。C[i,j] = max{C[i-1,j-1] + same{A[i],B[j]}, C[i -1,j], C[i,j-1]}.
实际求解的时候,可以用递推的方法,因为子问题大量重合的情况出现[这才是DP的真正含义]。算法复杂度O(MN).
回溯输出最长公共子序列过程:
算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(m + n)次就会遇到i = 0或j = 0的情况,此时开始返回。返回时与递归调用时方向相反,步数相同,故算法时间复杂度为Θ(m + n)。
void FindLCS(int m[][],int i, int j){ if (i == 0 || 0 == j){ return; } if (max is m[i - 1, j -1]){ FindLCS(m,i -1, j -1); if (a[i -1] == b[j -1 ]){ print "a[i -1]"; } }else if (max is m[i -1, j]){ FindLCS(i-1,j); if (a[i -1] == b[j]){ print "a[i -1]"; } }else if (max is m[i,j-1]){ FindLCS(i,j-1); if (a[i] == b[j -1 ]){ print "a[i]"; } } }
现在再来看最长递增子列的问题的求解:
C[i]代表A数组中第i个元素结尾的最长递增子列的长度(可知最后只需要求max{C[i], 0 < i < n}),注:想想最大子短和问题中,其状态也是如此设计的。
递推方程是:在所有的 0 < j < i中查找满足条件A[j] < A[i]的max C[j], 如果找到,那么C[i] = max{C[j] if A[j] < A[i]} + 1; else C[i] = 1; 代码实现非常简单,时间复杂度是O(n2).
如何进行优化呢?在计算每一个C[i]时,都要找出最大的C[j](j<i)来,由于C[j]没有顺序,只能顺序查找满足Aj<Ai最大的C[j],如果能将让C[j]有序,就可以使用二分查找,这样算法的时间复杂度就可能降到O(nlogn)。于是想到用一个数组B来存储“子序列的”最大递增子序列的最末元素,即有
B[C[j]] = Aj
在计算C[i]时,在数组B中用二分查找法找到满足j<i且B[C[j]]=Aj<Ai的最大的j,并将B[C[j]+1]置为Ai.这样,算法复杂度就降低为O(nLogn).
小结:DP问题的核心是设计状态和状态转移函数,条件有2个:1、状态需要无后效性 2、需要满足最优或者最优序列子结构。状态之间有大量的重叠这也是DP区别与一般递归问题最显著的特点。
int LIS(vector<int>& s) { // 不得不判斷的特例 if (s.size() == 0) return 0; // 先放入一個數字,免得稍後 v.back() 出錯。 vector<int> v; v.push_back(s[0]); for (int i = 1; i < s.size(); ++i) { int n = s[i]; if (n > v.back()) v.push_back(n); else *lower_bound(v.begin(), v.end(), n) = n; } return v.size(); }
转别人的:http://www.csie.ntnu.edu.tw/~u91029/LongestIncreasingSubsequence.html#3