动态规划

动态规划优化过程:

暴力递归 —>通过记忆计算过的数值减少迭代过程—>给计算过程规定好计算路线,使得后面的计算可以直接利用前面的结果。

第一道例题会列出这一优化过程(后面的只给出动态规划的求解)

Question1:有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。

暴力递归

int force(const vector<int>&penny,int index,int aim)
{
    //检测异常
    if(penny.size()<0||aim<0)return 0;

    int counts=0;
    //递归终点:确定了最后一种货币的数量。
    //并且如果aim为0 说明当前货币组合OK,counts记为1;
    if(index==penny.size())
        return counts=(aim==0?1:0);
    else{
        //penny[index]使用1个 使用2个 使用3个....
        for(int i=0;i<=aim/penny[index];++i)
            //penny[index+1]使用1个,使用2个 使用3个 对应aim-ipenny[index]
            counts+=force(penny,index+1,aim-i*penny[index]);
    }
    return counts;
}

记忆计算过的值

重复计算的情况: 使用了0张1元 0张 2元  2张5元 需要递归计算force(penny,3,90)//假设目标钱数是100

                                当使用0张1元  5张2元  0张5元 有需要递归计算force(penny,3,90)所以利用哈希表记录这个结果即可

int memory(const vector<int> &penny,int index,int aim)
{
    if(penny.size()<0||aim<0)
        return 0;
    map<pair<int,int>,int>memory;
    return recursion(penny,index,aim,memory);
}
int recursion(const vector<int> &penny,int index,int aim,
                                            map<pair<int,int>,int>memory)
{
    int counts=0;
    if(index==penny.size())
        return counts=(aim==0?1:0);
    else
    {    
        for(int i=0;i<=aim/penny[index];++i){
            //!=0 说明这个pair出现过,那么如果是-1 说明行不通 counts+0 如果是1那么counts+这个pair的值
            if(memory[make_pair(index+1,aim)]!=0)
                counts+=memory[make_pair(index+1,aim)]==-1?0:memory[make_pair(index+1,aim)];
            else
                counts+=force(penny,index+1,aim-i*penny[index]);
        }

    }
    //关于哈希表的下标操作阔以看看书
    memory[make_pair(index,aim)]=(counts==0?-1:counts);    
    return counts;
}

动态规划:关键就是构造dp矩阵:

1)两个可变参数就用二维矩阵,一个就用一维,后面的题会看到。

2)搞清楚dp[i][j]代表什么  比如此题 dp[i][j]代表 使用i种货币,换取j有多少种取法。最后dp[penny.size()][aim+1]就是答案

//动态规划的实现
int DP(const vector<int> &money_type,int aim)
{
	if(money_type.size()<0||aim<0)
		return 0;
	vector<vector<int>>dp_arry(money_type.size(),vector<int>(aim+1,0));
	//因为二维数组已经被初始化为0了 所以需要置0的不需要再处理了
	//直接置1就可以了
	for(int j=0;j<=aim;++j){
		if(j%money_type[0]==0)
			dp_arry[0][j]=1;
	}
	for(int i=1;i<money_type.size();++i)
	{
		//这里要注意 对于钱数是0  我们是有解决方案的 就是各拿0张所以方案数是1  但是对于钱数为1(2,5)是没有办法的 所以是0
		dp_arry[i][0]=1;
		for(int j=1;j<=aim;++j)
			dp_arry[i][j]=dp_arry[i-1][j]+(j-money_type[i]<0?0:dp_arry[i][j-money_type[i]]);
	}
	return dp_arry[money_type.size()-1][aim];
}



Question2:有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。

int countWays(int n){
	/*暴力递归
	//处理异常
	if(n<=0)return 0;
	//递归终止条件:指定一个台阶或者两个台阶的方法数
	if(n==1||n==2)
		return n;
	//每个台阶,都有两种方式可以到达
	//一步上来的,或者两步上来的。
	else{
		return countWays(n-1)+countWays(n-2);
	}*/
	vector<int>dp(n+1,0);
	//因为只有一个可变参数,台阶数,所以dp是一维的
	//dp[i]代表i阶台阶的方法数
   	dp[1]=1;dp[2]=2;
    for(int i=3;i<n+1;++i)
        dp[i]=(dp[i-1]+dp[i-2]);
    return dp[n];



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

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

变量只有一个,就是序列的长度,所以dp仍旧是一维的。dp[i]的意义:以A[i]元素作为最大元素的最长递增子序列的长度。

因为最长上升子序列并不一定包括最后一个数,所以最终答案也不是dp[n-1] 而是dp中的最大值

int getLIS(vector<int> A, int n){
    // write code here
    //初始化dp矩阵
    vector<int>dp(n,0);
    //第一个字符的最大递增子序列自然为1
    dp[0]=1;
    if(n==1)return 1;
    for(int loc=1;loc<n;++loc)
    {
        int max=0;
        for(int i=loc-1;i>=0;--i)
        {
            if(A[i]<A[loc]&&dp[i]>max)
                max=dp[i];
        }
        //这里如果dp[loc]是目前出现最小的,自然dp[loc]=0+1=1;
        //如果不是,就是取前面出现的比他小的那些值里对应dp最大的。dp[loc]=max+1;
        dp[loc]=max+1;
    }
    return *max_element(dp.cbegin(),dp.cend());
}



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

dp[i][j]的意义是: 到[i][j]位置的最小的路径和;

因为移动的方式是左—>右,上—>下。所以对于第一行的每个位置,都只能从第一行的左面移动而来,第一列的都只能是从第一列的上面移动而来。(即累加和)

根据第一行第一列的值,来求其他位置的值。 dp[i][j]可能是dp[i][j-1]+map[i][j] 即从左面移动而来,或者从上面移动而来dp[i-1][j]+map[i][j] 选取最小的作为结果。

int getMin(vector<vector<int> > map, int n, int m)
{
    // write code here
    //构建dp矩阵 并且全部初始化为0
    vector<vector<int>>dp(n,vector<int>(m,0));
    //构造dp矩阵的第一行
    for(int j=0;j<m;++j)
        dp[0][j]=map[0][j]+(j>0?dp[0][j-1]:0);
    //构造dp矩阵第一列
    for(int i=0;i<n;++i)
        dp[i][0]=map[i][0]+(i>0?dp[i-1][0]:0);
    //求其他位置的dp值
    for(int i=1;i<n;++i)
    { 
       for(int j=1;j<m;++j)
       {
           //因为移动顺序是左到右,上到下
           //所以任一格,都是由他上面一格或者左面一格移动过来的。
           //再加上该位置本身的权重。
           dp[i][j]=map[i][j]+min(dp[i-1][j],dp[i][j-1]);
       } 
    }
    return dp[n-1][m-1];
}



Question5:给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。

例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最长公共子序列。

两个字符串,dp自然是二维的。dp[i][j]的含义,A[0..i]和B[0..j]的最长公共子序列

首行:第一个元素:A[0]和B[0]的最长公共子序列,0/1  第二个元素: A[0]和B[0..1]的最长公共子序列 0/1 ...

其中一旦有一个值为1,自然后面的全是1,不需要再去判读。

首列同理。

对于dp[i][j] 如果A[i]=B[j] 那么dp[i][j]=dp[i-1][j-1]+1; 如果A[i]!=B[j] 则dp[i][j] 可能等于A[0..i]和B[0..j-1]的最长公共子串也可能等于A[0..i-1]和B[0...j]的最长公共子串,取二者的最大值。

在代码上通过添加一行一列的0 ,可以使首行首列的取值和其他行列的取值行为统一。

int findLCS(string A, int n, string B, int m){
    // write code here
    vector<vector<int>>dp(n+1,vector<int>(m+1,0));
    //设置第一例的值
/*    bool flag=false;
    for(int row=0;row<n;++row){
        if(A[row]==B[0]||flag){
            dp[row][0]=1;
            flag=true;
        }
    }
    flag=false;    
    //设置第一行的值
    for(int col=0;col<m;++col){
        if(B[col]==A[0]||flag){
            dp[0][col]=1;
            flag=true;
        }
    }*/
    //设置其他位置的值
    for(int row=1;row<=n;++row){
        for(int col=1;col<=m;++col){
            if(A[row-1]==B[col-1])
                dp[row][col]=dp[row-1][col-1]+1;
            else
                dp[row][col]=max(dp[row-1][col],dp[row][col-1]);
        }
    }
    return dp[n][m];
}



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

变量仍为两个 物品数和cap( w 和v 都是附属于物品的)。

dp[i][cur_cap]的意义是 有i种物品可选时,在当前的背包承重下,背包的最大价值。同样加入一行一列的0 来使每一个位置的操作统一。

对于dp[i][cur_cap] 如果将第i件物品放进去(前提是他放得进去):dp[i][cur_cap]=dp[i-1][cur_cap-w[i-1]]+v[i-1]背包减去i的重量,放i-1种物品时的最大价值+i的价值。不放第i件物品:那么i-1种物品在当前承重下的最大价值。取两者的最大值。

int maxValue(vector<int> w, vector<int> v, int n, int cap) {
    // write code here
    vector<vector<int>>dp(n+1,vector<int>(cap+1,0));
    //dp[row][col]的意义是 前row件物品在重量为col时的最大价值
    for(int i=1;i<=n;++i)
    {
        for(int cur_cap=1;cur_cap<=cap;++cur_cap)
        {
            //两种情况,第i件物品 加或者不加
            //在第i件物品可以放进背包的情况下,选取放或者不放的最大值
            if(cur_cap-w[i-1]>=0)
                dp[i][cur_cap]=
                max(dp[i-1][cur_cap],dp[i-1][cur_cap-w[i-1]]+v[i-1]);
            //放不进去的话,就是不用比较,直接取不放第i件物品的值
            else
                dp[i][cur_cap]=dp[i-1][cur_cap];
        }
    }
    return dp[n][cap];
}


通过上面的题也不难发现,动态规划其实和递归一样,也需要第一个基础的确定的值,(首行首列的值,或者第一个位置的值的定义)。




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

dp[i][j]代表什么大家可以结合上面的自己定义一下,这个题有四种可能的情况,算是最复杂的了,咱们李辉老师也讲过。

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,vector<int>(m+1,0));
    //首行代表:由空串变为目标串(一个字符组成的) 即插入操作
    for(int col=0;col<=m;++col)
        dp[0][col]=col*c0;
    //首列代表:由一个字符组成的串 变为目标串(空串) 即删除操作
    for(int row=0;row<=n;++row)
        dp[row][0]=row*c1;
    //定义其余位置
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            //dp[row][col]有四种情况
            //两大类A[i-1]?=B[j-1]
            if(A[i-1]==B[j-1])
                dp[i][j]=dp[i-1][j-1];
            else{
                //插入,删除,替换
                int temp=min(dp[i][j-1]+c0,dp[i-1][j]+c1);
                dp[i][j]=min(temp,dp[i-1][j-1]+c2);
            }
        }
    }
    return dp[n][m];
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值