最长公共子列问题-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).

回溯输出最长公共子序列过程:

flow

 

算法分析:
由于每次调用至少向上或向左(或向上向左同时)移动一步,故最多调用(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

posted @ 2013-02-28 00:40  一只会思考的猪  阅读(195)  评论(0编辑  收藏  举报