zoukankan      html  css  js  c++  java
  • 动态规划笔试题

    1、最长公共子序列、最长公共子串

    最长公共子序列(Longest-Common-Subsequence,LCS)

    dp[i][j]:dp[i][j]表示长度分别为i和j的序列X和序列Y构成的LCS的长度 dp[i][j] = 0,如果i=0 或 j=0 dp[i][j] = dp[i-1][j-1] + 1,如果 X[i-1] = Y[i-1] dp[i][j] = max{ dp[i-1][j], dp[i][j-1] },如果 X[i-1] != Y[i-1] LCS长度为 dp[Xlen][Ylen]

    View Code
    int dp[100][100];  // 存储LCS长度, 下标i,j表示序列X,Y长度 
    void LCS_dp(char * X, char * Y) 
    {
        int i, j;     
        int xlen = strlen(X);     
        int ylen = strlen(Y);       
        // dp[0-xlen][0] & dp[0][0-ylen] 都已初始化0     
        for(i = 1; i <= xlen; ++i)     
        {        
            for(j = 1; j <= ylen; ++j)     
            {             
                if(X[i-1] == Y[j-1]) 
                {                 
                    dp[i][j] = dp[i-1][j-1] + 1;             
                }
                else if(dp[i][j-1] > dp[i-1][j])             
                {                 
                    dp[i][j] = dp[i][j-1];             
                }
                else             
                {
                    dp[i][j] = dp[i-1][j];
                }         
            }     
        }     
        printf("len of LCS is: %d\n", dp[xlen][ylen]); 
        i = xlen; 
        j = ylen; 
        int k = dp[i][j]; 
        char lcs[100] = {'\0'}; 
        while(i && j) 
        {     
            if(X[i-1] == Y[j-1] && dp[i][j] == dp[i-1][j-1] + 1)     
            {         
                lcs[--k] = X[i-1];         
                --i; --j;     
            }
            else if(X[i-1] != Y[j-1] && dp[i-1][j] > dp[i][j-1])     
            {        
                --i;     
            }
            else     
            {         
                --j;     
            } 
        } 
        printf("%s\n",lcs);
    }

    最长公共子串(Longest-Common-Substring,LCS)

    dp[i][j]:表示X[0-i]与Y[0-j]的最长公共子串长度 dp[i][j] = dp[i-1][j-1] + 1,如果 X[i] == Y[j] dp[i][j] = 0,如果 X[i] != Y[j] 初始化:i==0或者j==0,如果X[i] == Y[j],dp[i][j] = 1;否则dp[i][j] = 0。

    最长公共子串的长度为max(dp[i][j])。

    View Code
    // 最长公共子串 DP
    int dp[100][100];
    void LCS_dp(char * X, char * Y) 
    {
        int xlen = strlen(X);
        int ylen = strlen(Y);
        int maxlen = 0;
        int maxindex = 0;     
        for(int i = 0; i < xlen; ++i)
        {
            for(int j = 0; j < ylen; ++j)
            {
                if(X[i] == Y[j])
                {
                    if(i && j)
                    {
                        dp[i][j] = dp[i-1][j-1] + 1;
                    }
                    if(i == 0 || j == 0)
                    {
                        dp[i][j] = 1;
                    }
                    if(dp[i][j] > maxlen)
                    {
                        maxlen = dp[i][j];
                        maxindex = i + 1 - maxlen;
                    }
                }
            }
        }
        if(maxlen == 0)
        {
            printf("NULL LCS\n");
            return;
        }
        printf("The len of LCS is %d\n",maxlen);
        int i = maxindex;
        while(maxlen--)
        {
            printf("%c",X[i++]);
        }
        printf("\n"); 
    }

    2、数组中最长递增子序列:如在序列1,-1,2,-3,4,-5,6,-7中,最长递增序列为1,2,4,6。

    时间复杂度O(N^2)的算法: LIS[i]:表示数组前i个元素中(包括第i个),最长递增子序列的长度 LIS[i] = max{ 1, LIS[k]+1 }, 0 <= k < i, a[i]>a[k]

    View Code
    int LIS(int a[], int length)
    {
        int *LIS = new int[length];
        for(int i = 0; i < length; ++i)
        {
            LIS[i] = 1; //初始化默认长度
            for(int j = 0; j < i; ++j) //前面最长的序列
                if(a[i] > a[j] && LIS[j]+1 > LIS[i])
                    LIS[i] = LIS[j]+1;  
        }
        int max_lis = LIS[0];
        for(int i = 1; i < length; ++i)
            if(LIS[i] > max_lis)
                max_lis = LIS[i];
        return max_lis;  //取LIS的最大值
    }

    时间复杂度O(NlogN)的算法: 辅助数组b[],用k表示数组b[]目前的长度,算法完成后k的值即为LIS的长度。 初始化:b[0] = a[0],k = 1 从前到后扫描数组a[],对于当前的数a[i],比较a[i]和b[k-1]: 如果a[i]>b[k-1],即a[i]大于b[]最后一个元素,b[]的长度增加1,b[k++]=a[i]; 如果a[i]<b[k-1],在b[1]...b[k]中二分查找第一个大于a[i]的数b[j],修改b[j]=a[i]。 LIS的长度为k

    View Code
    //修改的二分搜索算法,若要查找的数w在长为len的数组b中存在则返回下标
    //若不存在,则返回b数组中的第一个大于w的那个元素的下标
    int BiSearch(int *b, int len, int w)
    {
        int left = 0, right = len-1;
        int middle;
        while(left <= right)
        {
            middle = (left+right)/2;
            if(b[middle] > w)
                right = middle - 1;
            else if(b[middle] < w)
                left = middle + 1;
            else
                return middle;
        }
    
        //返回b数组中的刚刚大于w的那个元素的下标
        return (b[middle]>w) ? middle : middle+1;
    }
    
    int LIS(int *array, int n)
    {
        int *B = new int[n];
        int len = 1;
        B[0] = array[0];
    
        for(int i=1; i<n; ++i)
        {
            if(array[i] > B[len-1])
            {
                B[len] = array[i];
                ++len;
            }
            else
            {
                int pos = BiSearch(B, len, array[i]);
                B[pos] = array[i];
            }
        }
        delete []B;
        return len;
    }

    3、计算字符串的相似度(编辑距离)

    为了判断字符串的相似程度,定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为: 1.修改一个字符。2.增加一个字符。3.删除一个字符。

    比如,对于“abcdefg”和“abcdef”两个字符串来说,可以通过增加/减少一个“g“的方式来达到目的。上面的两种方案,都仅需要一次操作。把这个操作所需要的次数定义为两个字符串的距离,给定任意两个字符串,写出一个算法来计算出它们的距离。

    设 L(i,j)为使两个字符串和Ai和Bj相等的最小操作次数。 当ai==bj时 显然 L(i,j) = L(i-1,j-1) 当ai!=bj时 L(i,j) = min( L(i-1,j-1), L(i-1,j), L(i,j-1) ) + 1

    View Code
    int minValue(int a, int b, int c)
    {
        int t = a <= b ? a:b;
        return t <= c ? t:c;
    }
    
    int calculateStringDistance(string strA, string strB)
    {
        int lenA = (int)strA.length()+1;
        int lenB = (int)strB.length()+1;
    
        int **c = new int*[lenA];
        for(int i = 0; i < lenA; i++)
            c[i] = new int[lenB];
    
        for(int i = 0; i < lenA; i++) c[i][0] = i;
        for(int j = 0; j < lenB; j++) c[0][j] = j;
        c[0][0] = 0;
        for(int i = 1; i < lenA; i++)
        {
            for(int j = 1; j < lenB; j++)
            {
                if(strB[j-1] == strA[i-1])
                    c[i][j] = c[i-1][j-1];
                else
                    c[i][j] = minValue(c[i][j-1], c[i-1][j], c[i-1][j-1]) + 1;
            }
        }
    
        int ret =  c[lenA-1][lenB-1];
    
        for(int i = 0; i < lenA; i++)
            delete [] c[i];
        delete []c;
    
        return ret;
    }

    4、8*8的棋盘上面放着64个不同价值的礼物,每个小的棋盘上面放置一个礼物(礼物的价值大于0),一个人初始位置在棋盘的左上角,每次他只能向下或向右移动一步,并拿走对应棋盘上的礼物,结束位置在棋盘的右下角,请设计一个算法使其能够获得最大价值的礼物。

    动态规划算法:   dp[i][j] 表示到棋盘位置(i,j)上可以得到的最大礼物值   dp[i][j] = max( dp[i][j-1] , dp[i-1][j] ) + value[i][j]  (0<i,j<n) 

    View Code
    int GetMaxValue(int **dp, int **value)
    {
        int i, j, n = 8;  
        dp[0][0] = value[0][0];  
        for(i = 1; i < n; i++)  
        {  
            dp[i][0] = dp[i-1][0] + value[i][0];  
        }  
        for(j = 1; j < n; j++)  
        {  
            dp[0][j] = dp[0][j-1] + value[0][j];  
        }  
    
        for(i = 1; i < n; i++)  
        {  
            for(j = 1; j < n; j++)  
            {  
                dp[i][j] = max(dp[i][j-1] , dp[i-1][j]) + value[i][j];  
            }  
        }  
        return dp[n-1][n-1];
    }

    5、给定一个整数数组,求这个数组中子序列和最大的最短子序列,如数组a[]={1,2,2,-3,-5,5}子序列和最大为5,最短的为a[5]。

    动态规划   sum[i] = max(sum[i-1]+a[i], a[i]) (sum[0]=a[0],1<=i<=n)  len[i] = max(len[i-1]+1, 0) (len[0]=0,1<=i<=n)

    View Code
    void max_sub(int a[], int size)
    {
        int *sum = new int[size];
        int *len = new int[size];
        int temp_sum = 0;
    
        sum[0] = a[0];
        len[0] = 0;
        for(int i = 1; i < size; i++)
        {
            temp_sum = sum[i-1] + a[i];
            if(temp_sum > a[i])
            {
                sum[i] = temp_sum;
                len[i] = len[i-1]+1;
            }
            else
            {
                sum[i] = a[i];
                len[i] = 0;
            }
        }
        int index = 0;
        for(int i = 1; i < size; i++)
        {
            if(sum[i] > sum[index])
                index = i;
            else if(sum[i] == sum[index] && len[i] < len[index])
                index = i;
        }
        printf("Max sub sum is %d, from %d to %d",sum[index],index-len[index],index);
    
        delete []sum;
        delete []len;
    }

    6、子数组的最大和

    状态方程: Start[i] = max{A[i], Start[i-1]+A[i]} All[i] = max{Start[i], All[i-1]}

    View Code
    int MaxSum(int *A, int n)
    {
        int * All = new int[n];
        int * Start = new int[n];
    
        All[0] = Start[0] = A[0];
        for(int i=1; i<n; ++i)
        {
            Start[i] = max(A[i], A[i]+Start[i-1]);
            All[i] = max(Start[i], All[i-1]);
        }
        int max = All[n-1];
        delete []All;
        delete []Start;
        return max;
    }

    因为Start[i-1]只在计算Start[i]时使用,而且All[i-1]也只在计算All[i]时使用,所以可以只用两个变量就够了,节省空间。

    View Code
    int MaxSum(int *A, int n)
    {
        int All = A[0];
        int Start = A[0];
        for(int i=1; i<n; ++i)
        {
            Start = max(A[i], A[i]+Start);
            All = max(Start, All);
        }
        return All;
    }

    7、在数组中,数字减去它右边的数字得到一个数对之差。求所有数对之差的最大值。例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中,数对之差的最大值是11,是16减去5的结果。

    思路:假设f[i]表示数组中前i+1个数的解,前i+1个数的最大值为m[i]。则状态转移方程: f[i] = max(f[i-1], m[i-1] - a[i]), m[i] = max(m[i-1],a[i])。问题的解为f[n-1]。

    View Code
    int MaxDiff_Solution1(int *pArray, int nLen)  
    {  
        if(pArray == NULL || nLen <= 1)  
            return 0;  
        int *f = new int[nLen];  
        int *m = new int[nLen];  
      
        f[0] = 0;          //1个数的情况   
        m[0] = pArray[0];  
        for(int i = 1; i < nLen; i++)  
        {  
            f[i] = max(f[i-1], m[i-1] - pArray[i]);  
            m[i] = max(m[i-1], pArray[i]);  
        }  
        return f[nLen - 1];  
    }

    上述代码用了两个辅助数组,其实只需要两个变量,前i个数的情况只与前i-1个数的情况有关。在“子数组的最大和问题”中,也使用过类似的技术。

    View Code
    int MaxDiff_Solution2(int *pArray, int nLen)  
    {
        if(pArray == NULL || nLen <= 1)  
            return 0;  
        int f = 0;  
        int m = pArray[0];  
        for(int i = 1; i < nLen; i++)  
        {  
            f = max(f, m - pArray[i]);  
            m = max(m, pArray[i]);  
        }  
        return f;  
    }

    8、从一列数中筛除尽可能少的数使得从左往右看,这些数是从小到大再从大到小的。

    双端 LIS 问题,用 DP 的思想可解,目标规划函数 max{ b[i] + c[i] - 1 }, 其中 b[i] 为从左到右,0--i 个数之间满足递增的数字个数;c[i] 为从右到左,n-1--i个数之间满足递增的数字个数。最后结果为 n-max 。

    View Code
    /* 
    a[] holds the original numbers
    b[i] holds the number of increasing numbers from a[0] to a[i]
    c[i] holds the number of increasing numbers from a[n-1] to a[i]
    */
    int double_lis(int a[], int n)
    {
        int *b = new int[n];
        int *c = new int[n];
    
        // update array b from left to right
        for(int i = 0; i < n; ++i)
        {
            b[i] = 1;
            for(int j = 0; j < i; ++j)
                if(a[i] > a[j] && b[j]+1 > b[i])
                    b[i] = b[j] + 1;
        }
    
        // update array c from right to left
        for (int i = n-1; i >= 0; --i)
        {
            c[i] = 1;
            for(int j = n-1; j > i; --j)
                if(a[i] > a[j] && c[j]+1 > c[i])
                    c[i] = c[j] + 1;
        }
    
        int max = 0;
        for (int i = 0; i < n; ++i )
        {
            if (b[i]+c[i] > max)
                max = b[i] + c[i];
        }
    
    
        max = max-1; //delete the repeated one
        delete []b;
        delete []c;
    
        return n-max;
    }

    9、从给定的N个正数中选取若干个数之和最接近M

    解法:转换成01背包问题求解,从正整数中选取若干个数放在容量为M的背包中。

    View Code
    #include <stdio.h>
    
    const int MAX = 10010;
    int f[MAX];
    int g[MAX][MAX];
    
    int main()
    {
        //从数组value中选中若干个数之和最接近V
        int value[] = {2,9,5,7,4,11,10};
        int V = 33;   //子集和
        int N = sizeof(value)/sizeof(value[0]);
    
        for(int i = 0; i <= V; ++i)  //初始化:没要求和一定是V
        {
            f[i] = 0;
        }
        for(int i = 0; i < N; ++i)
        {
            for(int v = V; v >= value[i]; --v)
            {
                if(f[v] < f[v-value[i]] + value[i] ) //选value[i]
                {
                    f[v] = f[v-value[i]] + value[i];
                    g[i][v] = 1;
                }
                else         //不选value[i]
                {
                    f[v] = f[v]; 
                    g[i][v] = 0;
                }
            }
        }
        printf("%d\n",f[V]);
    
        int i = N; //输出解
        int v = V;
        while(i-- > 0)
        {
            if(g[i][v] == 1)
            {
                printf("%d, ",value[i]);
                v -= value[i];
            }
        }
        printf("\n");
        return 0;
    }

    从给定的N个正数中选取若干个数之和为M

    View Code
    #include <iostream>
    #include <list>
    using namespace std;
    
    void find_seq(int sum, int index, int * value, list<int> & seq)
    {
        if(sum <= 0 || index < 0) return;
        if(sum == value[index])
        {
            printf("%d ", value[index]);
            for(list<int>::iterator iter = seq.begin(); iter != seq.end(); ++iter)
            {
                printf("%d ", *iter);
            }
            printf("\n");
        }
        seq.push_back(value[index]); 
        find_seq(sum-value[index], index-1, value, seq); //放value[index]
        seq.pop_back();
        find_seq(sum, index-1, value, seq); //不放value[index]
    }
    
    int main()
    {
        int M;
        list<int> seq;
        int value[] = {2,9,5,7,4,11,10};
        int N = sizeof(value)/sizeof(value[0]);
        for(int i = 0; i < N; ++i)
        {
            printf("%d ",value[i]);
        }
        printf("\n");
        scanf("%d", &M);
        printf("可能的序列:\n");
        find_seq(M, N-1, value, seq);
    
        return 0;
    }

    10、将一个较大的钱,不超过1000的人民币,兑换成数量不限的100、50、10、5、2、1的组合,请问共有多少种组合呢?

    解法:01背包中的完全背包问题(即每个物品的数量无限制) dp[i][j]:表示大小为j的价值用最大为money[i]可表示的种类数

    View Code
    #define NUM 7
    int money[NUM] = {1, 2, 5, 10, 20, 50, 100};  
    // 动态规划解法(完全背包)   
    int NumOfCoins(int value)  
    {  
        int dp[7][1010];  
        for(int i = 0; i <= value; ++i)
            dp[0][i] = 1;  
      
        for(int i = 1; i < NUM; ++i)
        {
            for(int j = 0; j <= value; ++j)  
            {
                if(j >= money[i])  
                    dp[i][j] = dp[i][j-money[i]] + dp[i-1][j];  
                else  
                    dp[i][j] = dp[i-1][j];  
            }  
        }  
        return dp[6][value];
    }

    11、捞鱼问题:20个桶,每个桶中有10条鱼,用网从每个桶中抓鱼,每次可以抓住的条数随机,每个桶只能抓一次,问一共抓到180条的排列有多少种。

    分析:看看这个问题的对偶问题,抓取了180条鱼之后,20个桶中剩下了20条鱼,不同的抓取的方法就对应着这些鱼在20个桶中不同的分布,于是问题转化为将20条鱼分到20个桶中有多少中不同的分类方法(这个问题当然也等价于180条鱼分到20个桶中有多少种不同的方法)。

    dp[i][j]:前i个桶放j条鱼的方法共分为11种情况:前i-1个桶放j-k(0<=k<=10)条鱼的方法总和。我们可以得到状态方程:f(i,j) = sum{ f(i-1,j-k), 0<=k<=10}

    View Code
    /*捞鱼:将20条鱼放在20个桶中,每个桶最多可以放10条,求得所有的排列方法
    /*自底向上DP f(i,j) = sum{ f(i-1,j-k), 0<=k<=10 }
    /*该方法中测试 20个桶 180条鱼,与递归速度做对比
    */
    void CatchFish()
    {
        int dp[21][200]; // 前i个桶放j条鱼的方法数
        int bucketN = 20;
        int fishN = 20;
        memset(dp,0,sizeof(dp));
        for(int i = 0; i <= 10; ++i)  // 初始化合法状态
        {
            dp[1][i] = 1;
        }
        for(int i = 2; i <= bucketN; ++i)  // 从第二个桶开始
        {
            for(int j = 0; j <= fishN; ++j)
            {
                for(int k = 0; k <= 10 && j-k >= 0; ++k)
                {
                    dp[i][j] += dp[i-1][j-k];
                }
            }
        }
        printf("%d\n",dp[bucketN][fishN]);
    }

    12、n个骰子的点数:把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的出现的值。

    F(k,n) 表示k个骰子点数和为n的种数,k表示骰子个数,n表示k个骰子的点数和 对于 k>0, k<=n<=6*k F(k,n) = F(k-1,n-6) + F(k-1,n-5) + F(k-1,n-4) + F(k-1,n-3) + F(k-1,n-2) + F(k-1,n-1)  对于 n<k or n>6*k F(k,n) = 0 当k=1时, F(1,1)=F(1,2)=F(1,3)=F(1,4)=F(1,5)=F(1,6)=1

    View Code
    void SumOfDices()
    {
        int dp[21][6*20+1]; // k个骰子,和为n的种类数,不超过20个骰子
        int number = 3;  // 骰子数
        int face = 6;   // 面数,6面
        memset(dp,0,sizeof(dp));
        for(int i = 1; i <= 6; ++i)  // 初始化1个骰子的情况
        {
            dp[1][i] = 1;
        }
        for(int i = 2; i <= number; ++i)  // 从第二个骰子开始
        {
            for(int j = i; j <= face * i; ++j) // i个骰子的点数从i到i*6
            {
                for(int k = 1; k <= face && j-k >= 0; ++k)
                {
                    dp[i][j] += dp[i-1][j-k];
                }
            }
        }
        for(int i = 0; i <= number * face; ++i)
        {
            printf("Sum = %d, Number is %d\n",i,dp[number][i]);
        }
    }

    13、给定三个字符串A,B,C;判断C能否由AB中的字符组成,同时这个组合后的字符顺序必须是A,B中原来的顺序,不能逆序;例如:A:mnl,B:xyz;如果C为mnxylz,就符合题意;如果C为mxnzly,就不符合题意,原因是z与y顺序不是B中顺序。

    DP求解:定义dp[i][j]表示A中前i个字符与B中前j个字符是否能组成C中的前(i+j)个字符,如果能标记true,如果不能标记false; 有了这个定义,我们就可以找出状态转移方程了,初始状态dp[0][0] = 1: dp[i][j] = 1 如果 dp[i-1][j] == 1 && C[i+j-1] == A[i-1] dp[i][j] = 1 如果 dp[i][j-1] == 1 && C[i+j-1] == B[j-1]

    View Code
    #include <iostream>
    using namespace std;
    
    char A[201]; 
    char B[201];
    char C[401];    
    int dp[201][201];   // dp[i][j] 表示A前i个字符与B前j个字符是否能构成C前i+j个字符
    
    int main() 
    {
        memset(dp,0,sizeof dp); 
        scanf("%s %s %s", A, B, C); 
        int lenA = strlen(A); 
        int lenB = strlen(B);
        dp[0][0] = 1;       
        for(int i = 0; i <= lenA; ++i) 
        {
            for(int j = 0; j <= lenB; ++j) 
            {
                if(i > 0 && (dp[i-1][j] == 1) && (C[i+j-1] == A[i-1])) 
                { 
                    dp[i][j] = 1;
                }
                if(j > 0 && (dp[i][j-1] == 1) && (C[i+j-1] == B[j-1]))
                { 
                    dp[i][j] = 1; 
                } 
            }
        }
        printf("%s\n",dp[lenA][lenB] ? "yes" : "no");
        return 0; 
    }

     转自:http://www.cnblogs.com/luxiaoxun/archive/2012/11/15/2771605.html

  • 相关阅读:
    跃迁方法论 Continuous practice
    EPI online zoom session 面试算法基础知识直播分享
    台州 OJ 2648 小希的迷宫
    洛谷 P1074 靶形数独
    洛谷 P1433 DP 状态压缩
    台州 OJ FatMouse and Cheese 深搜 记忆化搜索
    台州 OJ 2676 Tree of Tree 树状 DP
    台州 OJ 2537 Charlie's Change 多重背包 二进制优化 路径记录
    台州 OJ 2378 Tug of War
    台州 OJ 2850 Key Task BFS
  • 原文地址:https://www.cnblogs.com/liangzh/p/3097510.html
Copyright © 2011-2022 走看看