算法——动态规划基本题型1 动态规划系列问题-最小编辑代价

1.最长上升子序列(LIS)

一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

方法一:O(n2)算法:

class AscentSequence {
public:
    int findLongest(vector<int> A, int n) {
        // write code here
        vector<int> dp(n,1);
        for(int i=0;i<n;i++)         //循环n次
            for(int j=i-1;j>=0;j--)    //比拉力前面所有的元素
                if(A[j]<A[i])
                    dp[i]=max(dp[i],dp[j]+1);
        int res = 0;
        for(int i=0;i<n;i++)
            res = max(res,dp[i]);
        return res;
    }
};

方法二:O(n*logn)算法

使用辅助空间!!
我们定义一个序列B,然后令 i = 0 到 8 逐个考察这个序列。 
此外,我们用一个变量 end来记录B中最后一个数的下标。

首先,令B[0] = A[0] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2,这时end=0。

然后,考察A[1],可以将A[]有序地放到B里,令B[0] = 1,就是说长度为1的LIS的最小末尾是1,B[0]=2已经被淘汰了,这时 end =1。

接着,A[2] = 5,A[2]>B[0],所以令B[1]=A[2]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[0..1] = 1, 5, end =1。

再来,A[3] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[0..1] = 1, 3, end = 2。 

继续,A[4] = 6,比B中最大的数3还要大 ,于是很容易可以推知B[2] = 6, 这时B[0..2] = 1, 3, 6, end = 2。 

A[5] = 4,它在3和6之间,于是我们就要把6替换掉,得到B[2] = 4,B[0..2] = 1, 3, 4, end 继续等于2。 

A[6] = 8,它很大,比4大。于是继续往B中追加,B[3] = 8, end 变成3了。 

A[7] = 9,得到B[4] = 9,此时B是1,3,4,8,9,end是4。 

最后一个, A[8] = 7,它在4和8之间,所以我们知道,最新的B[3] =7,B[0..4] = 1, 3, 4, 7, 9, end = 4。 

于是我们知道了LIS的长度为 end+1 = 5 。 

注意:  这个1,3,4,7,9不是LIS字符串,比如本题例的LIS应该是1,3,4,8,9,7代表的意思是存储4位长度LIS的最小末尾是7,  所以我们的B数组,是存储对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个A[8] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到B[4], 9更新到B[5],得出LIS的长度为6。 

然后应该发现一件事情了:在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说, 我们可以使用  二分查找  ,将每一个数字的插入时间优化到  O(logN),于是算法的时间复杂度就降低到了 O(NlogN)。
class AscentSequence {
public:
    int findLongest(vector<int> A, int n) {
        // write code here
        if(A.empty())
            return 0;
        vector<int> B(n+1,0);    //新建数组,用于统计
        int len = 1;
        B[1] = A[0];
        for(int i=1;i<n;i++)
        {
            if(A[i]>B[len])
                B[++len]=A[i];     //如果比末尾元素还大的话,直接添加到尾部
            else
            {
                int left = 1,right = len;    //否则,二分查找合适的位置
                while(left<right)
                {
                    int mid = (left+right)/2;
                    if(B[mid]>=A[i])
                        right = mid;
                    else
                        left = mid +1;
                }
                B[right]=A[i];
            }
        }
        return len;
    }
};


2.最长公共子序列(LCS)

给定两个字符串,求解这两个字符串的最长公共子序列(Longest Common Sequence)。比如字符串1:BDCABA;字符串2:ABCBDAB
则这两个字符串的最长公共子序列长度为4,最长公共子序列是:BCBA

class LCS {
public:
    int findLCS(string A, int n, string B, int m) {
        // write code here
        vector<vector<int>> lcs(n+1);   //设置动态规划数组
        for(int i=0;i<n+1;i++)
            lcs[i].resize(m+1,0);
        for(int i=1;i<n+1;i++)
            for(int j=1;j<m+1;j++)
            {
                if(A[i-1]==B[j-1])
                    lcs[i][j]=lcs[i-1][j-1]+1;
                else
                    lcs[i][j]=max(lcs[i-1][j],lcs[i][j-1]);
            }
        return lcs[n][m];     //返回最长公共子序列的值
    }
};

3.最长公共子串

注意,这里是子串。子串必须是连着的,而不像子序列!!!这一点至关重要~~~

class LongestSubstring {
public:
    int findLongest(string A, int n, string B, int m) {
        // write code here
        vector<vector<int>> dp(n+1);
        for(int i=0;i<n+1;i++)
            dp[i].resize(m+1,0);
        int res = 0;
        for(int i=1;i<n+1;i++)
            for(int j =1;j<m+1;j++)
            {
                if(A[i-1]==B[j-1])
                {
                    dp[i][j]=dp[i-1][j-1]+1;              //这里至关重要
                    res = max(res,dp[i][j]);
                }
                else
                    dp[i][j] = 0;                         //如果不等,直接为0
            }
        return res;
    }
};

4.最小编辑代价

对于两个字符串A和B,我们需要进行插入、删除和修改操作将A串变为B串,定义c0,c1,c2分别为三种操作的代价,请设计一个高效算法,求出将A串变为B串所需要的最少代价。

给定两个字符串AB,及它们的长度和三种操作代价,请返回将A串变为B串所需要的最小代价。

测试样例:
"abc",3,"adc",3,5,3,100
返回:8

动态规划系列问题-最小编辑代价

class MinCost {
public:
    int findMinCost(string A, int n, string B, int m, int c0, int c1, int c2) {
        // write code here
        vector<vector<int>> dp(n+1);   //dp[i][j] 的含义是从A[0...i]变化到B[0...j]所需的最小代价
        for(int i=0;i<n+1;i++)
            dp[i].resize(m+1,0);
        for(int j=1;j<m+1;j++)       //初始化代价
            dp[0][j]=j*c0;
        for(int i=1;i<n+1;i++)
            dp[i][0] = i*c1;
        for(int i=1;i<n+1;i++)
            for(int j=1;j<m+1;j++)
            {
                if(A[i-1]==B[j-1])                 //先确定修改方式带来的代价
                    dp[i][j] = dp[i-1][j-1];
                else
                    dp[i][j] = dp[i-1][j-1]+c2;   
                dp[i][j] = min(dp[i][j],dp[i-1][j]+c1);   //对比删除操作
                dp[i][j] = min(dp[i][j],dp[i][j-1]+c0);   //对比插入操作
            }
        return dp[n][m];
    }
};

5.字符串交错组成

对于三个字符串A,B,C。我们称C由A和B交错组成当且仅当C包含且仅包含A,B中所有字符,且对应的顺序不改变。请编写一个高效算法,判断C串是否由A和B交错组成。

给定三个字符串A,BC,及他们的长度。请返回一个bool值,代表C是否由A和B交错组成。保证三个串的长度均小于等于100。

测试样例:
"ABC",3,"12C",3,"A12BCC",6
返回:true

class Mixture {
public:
    bool chkMixture(string A, int n, string B, int m, string C, int v) {
        // write code here
        vector<vector<int>> dp(n+1);   //dp[i][j]  表示A串的第i个字符之前的串和B串的第j个字符串之前的串组合起来得到当前串C[i+j]
        for(int i=0;i<=n;i++)
            dp[i].resize(m+1,0);
        dp[0][0] = 1;
        for(int i=0;i<=n;i++)
            for(int j=0;j<=m;j++)
            {
                if(dp[i][j])   //相当于判断前面一步是否可以出厂当前串
                {
                    if(i<n&&A[i]==C[i+j])
                        dp[i+1][j] = 1;
                    if(j<m&&B[j]==C[i+j])
                        dp[i][j+1] = 1;
                }
            }
        return dp[n][m];
    }
};



猜你喜欢

转载自blog.csdn.net/super_gk/article/details/80940507
今日推荐