牛客网算法学习笔记-动态规划(2)

来源:互联网 发布:淘宝望远镜 编辑:程序博客网 时间:2024/05/20 16:33

案例一  台阶问题

题目:有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。为了防止溢出,请将结果Mod 1000000007

给定一个正整数int n,请返回一个数,代表上楼的方式数。保证n小于等于100000。


暴力搜索方法:

假设第i层台阶的方法数为f(i),则f(i)=f(i-1)+f(i-2),第i层只能等于i-1层上一阶加上i-2层上两阶,因此:

f(1)=1

f(2)=2

f(i)=f(i-1)+f(i-2)

代码如下:

class GoUpstairs {
public:
    int countWays(int n) {
        if(n<1)
            return 0;
        if(n==1||n==2)
            return n;
        return countWays(n-1)+countWays(n-2);
    }
};

动态规划方法:

根据f(i)=f(i-1)+f(i-2)直接写出即可:

class GoUpstairs {
public:
    int countWays(int n) {
        if(n<1) return 0;
        if(n==1||n==2)
            return n;
        int res[n+1];
        res[1]=1;
        res[2]=2;
        for(int i=3;i<=n;i++)
            res[i]=(res[i-1]+res[i-2])%1000000007;
        return res[n];
    }
};

案例二 矩阵最小路径和

题目:

有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。

给定一个矩阵map及它的行数n和列数m,请返回最小路径和。保证行列数均小于等于100

动态规划方法:建立一个与矩阵map一样大的矩阵dp[n][m],其中dp[i][j]表示到达位置(i,j)的最小路径之和。

dp[0][0]=map[0][0]                     //这个不用解释了吧

dp[0][j]=map[0][j]+map[0][j-1]    0<j<=m-1      //沿着第一行走,最短路径只能是累加

dp[i][0]=map[i][0]+map[i-1][0]     0<i<=n-1      //沿着第一列走,最短路径只能是累加

dp[i][j]=min(dp[i-1][j],dp[i][j-1])+map[i][j]      1<=i<=n-1,1<=j<=m-1  //其余的位置(i,j),只能从他上面的位置(i-1,j)或者是他左边的位置(i,j-1)走过来,所以选其中最小的加上现在位置的值就是该位置的最小路径和

class MinimumPath {
public:
    int getMin(vector<vector<int> > map, int n, int m) {
        vector<vector<int> > res;
        res.resize(n);
        for (int i = 0; i < n; ++i){
            res[i].resize(m);
        }
        res[0][0]=map[0][0];
        for(int i=1;i<n;i++)
            res[i][0]=res[i-1][0]+map[i][0];
        for(int j=1;j<m;j++)
            res[0][j]=res[0][j-1]+map[0][j];
        for(int i=1;i<n;i++)
            for(int j=1;j<m;j++)
                res[i][j]=min(res[i-1][j],res[i][j-1])+map[i][j];
        return res[n-1][m-1];
    }
};


案例三 最长上升子序列问题

题目:这是一个经典的LIS(即最长上升子序列)问题,请设计一个尽量优的解法求出序列的最长上升子序列的长度。

给定一个序列A及它的长度n(长度小于等于500),请返回LIS的长度。

思路:申请一个dp[n],dp[i]的含义为:以A[i]这个数结尾的情况下,A[0,...,i]中的最大递增子序列的长度

dp[i]=max{dp[j]+1, 0<=j<i, A[j]<A[i]}


class LongestIncreasingSubsequence {
public:
    int getLIS(vector<int> A, int n) {
        int dp[n];
        dp[0]=1;
        int res=0;
        for(int i=1;i<n;i++){
            int max=0;
            for(int j=0;j<i;j++){
                if(A[i]>A[j]&&max<dp[j])
                    max=dp[j];
            }
            dp[i]=max+1;
        }
        for(int i=0;i<n;i++)
            res=res>dp[i]?res:dp[i];
        return res;
    }
};

案例四 最长公共子序列问题

题目:给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最长公共子序列。

给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。保证两串长度均小于等于300。

思路:建立一个大小为n*m的矩阵dp,dp[i][j]的含义是:字符串A[0,...,i]与B[0,...,j]的最长公共子序列的长度,那么

1. dp的第一列为dp[i][0],代表A[0,...,i]与B[0]之间的最长公共子序列,假设A[i]==B[0],那么dp[i][0]=1,且dp[i,...n-1][0]=1

2. dp的第一行为dp[0][j],代表A[0]与B[0,...,j]之间的最长公共子序列,假设A[0]==B[j],那么dp[0][j]=1,且dp[0][j,...m-1]=1

3. dp[i][j]的值分为以下两种情况:

    dp[i][j]=max(dp[i-1][j],dp[i][j-1])  //当A[i]!=B[j]的时候

    dp[i][j]=dp[i-1][j-1]+1                  //当A[i]==B[j]的时候

class LCS {
public:
    int findLCS(string A, int n, string B, int m) {
        int dp[n+1][m+1];
        memset(dp,0,sizeof(dp));
        
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++){
                if(A[i]!=B[j]) 
                    dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
                else
                    dp[i+1][j+1]=dp[i][j]+1;
            }
        return dp[n][m];
    }
};


案例五  背包问题

题目:一个背包有一定的承重cap,有N件物品,每件都有自己的价值,记录在数组v中,也都有自己的重量,记录在数组w中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。

给定物品的重量w价值v及物品数n和承重cap。请返回最大总价值。

思路:申请一个大小为N*W的矩阵dp,dp[i][j]表示当重量不超过j时前i件物品的最大值,他有两种情况:

1. 第i件物品确定装入背包,那么前i-1个物品的重量不能超过j-w[i],此时dp[i][j]=dp[i-1][j-w[i]+v[i]

2. 第i件物品不装入背包,那么前i-1个物品的重量不超过j即可,此时dp[i][j]=dp[i-1][j]

dp[i][j]就取二者中的最大值即可

class Backpack {
public:
    int maxValue(vector<int> w, vector<int> v, int n, int cap) {
        int dp[n+1][cap+1];
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
            for(int j=1;j<=cap;j++){
                if(j>=w[i-1])
                    dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]);
                else
                    dp[i][j]=dp[i-1][j];
            }
                
        return dp[n][cap];
    }
};


案例六  最优编辑问题

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

给定两个字符串A和B,及它们的长度和三种操作代价,请返回将A串变为B串所需要的最小代价。保证两串长度均小于等于300,且三种代价值均小于等于100。

思路:假设A,B的长度分布为N和M,那么建立一个大小为(N+1)*(M+1)的矩阵dp,其中dp[i][j]表示的含义是将A[0,...,i-1]编辑成B[0,...,j-1]的最小代价

dp[0][0]=0

dp[i][0]=c1*i

dp[0][j]=c0*i

dp[i][j]有四种情况分别如下:

1. dp[i][j]=dp[i-1][j]+c1    

2. dp[i][j]=dp[i][j-1] +c2  

3. 当A[i-1]==B[j-1]时,dp[i][j]=dp[i-1][j-1]   

4. 当A[i-1]!=B[j-1]时,dp[i][j]=dp[i-1][j-1] +c2  


class MinCost {
public:
    int findMinCost(string A, int n, string B, int m, int c0, int c1, int c2) {
        int dp[n+1][m+1];
        dp[0][0]=0;
        for(int i=1;i<=n;i++)
            dp[i][0]=i*c1;
        for(int j=1;j<=m;j++)
            dp[0][j]=j*c0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++){
                int temp=min(dp[i-1][j]+c1,dp[i][j-1]+c0);
                if(A[i-1]==B[j-1]){
                    dp[i][j]=min(temp,dp[i-1][j-1]);
                }
                else
                    dp[i][j]=min(temp,dp[i-1][j-1]+c2);
            }
        return dp[n][m];
    }
};


以上就是一些经典动态规划算法的学习笔记,如果有问题请各位指正,多多交流!

原创粉丝点击