动态规划问题总结(一)背包问题

求解方法

动态规划问题求解五部曲

1.dp数组、下标i,j的含义

2.递推公式

3.dp数组如何初始化

4.遍历顺序

5.打印dp数组(防止出错)

走楼梯问题

每次走楼梯,要么一次迈一步,要么一次迈两步,n阶楼梯有多少种走法?

分析:

最后一步要么走一步,要么走两步,n阶楼梯的走法就等于n-1阶的走法加上n-2阶的走法,即f(n)=f(n-1)+f(n+2)(递推公式)f(n)就是dp数组,代表这n阶楼梯的总走法,f(1)=1,f(2)=2[初始化]

背包问题

0-1背包

        0-1背包问题是经典的动态规划问题之一。在i个物品中挑选,放入容量为m的书包中所达到的最大价值。其中每个物品只能挑选一次。

二维数组

dp[i][j],是指从i个物品中去选择,当背包容量为j时,所装的物品的最大价值

书包容量为j时

不放物品i时,最大价值=dp[i-1][j]     [从(i-1)件物品中去选择的最大价值]

包含物品i时,最大价值=max( dp[i-1][j] ,  dp[i-1][  j-weight[i] ]+value[i] )

如果说当书包已经从i-1件物品中选出最大value的组合,且容量所剩不大时,还想把物品i放进去,肯定是要把其他物品拿出来,但是拿出来的物品和刚放进去的物品i的value大小不同,不能直接确定总value一定变大还是变小,要有比较。

dp[i-1][ j-weight[i] ]+value[i] 就是指提前预留好物品i的空间,书包中可以放其他物品的容量变成了j-weight[i],等价于书包容量为j-weight[i]时从i-1个物品中选出的最大价值:dp[i-1][ j-weight[i] ]再加上物品i的价值,这就是一定包含物品i的总value。

如果说书包容量不大,

并且拿出来的物品value比新加物品i的value大(物品i不放更好)时,这时候的最大价值就是不放物品i时的价值=dp[i-1][j];

同理如果拿出来的物品value比新加物品i的value小,此时的最大value就是一定包含物品i的情况;

但是当书包容量很大,物品i可以直接放进去,多加一个物品,书包中的价值肯定是变大了,此时的最大价值一定包含物品i,即dp[i-1][ j-weight[i] ]+value[i];

所以dp[i][j]=max(dp[i-1][j] ,  dp[i-1][ j-weight[i] ]+value[i] )

初始化

for (int i = 1; i < weight.size(); i++) {  //j=0时
    dp[i][0] = 0;
}
//i=0时
for (int j = weight[0]; j <= bagweight; j++) {
    dp[0][j] = value[0];
}

遍历顺序

无论是先遍历物品还是书包都是一样的

// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
    for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
        if (j < weight[i]) //书包装不下物品i,j-weight[i]<0,数组会越界;
        dp[i][j] = dp[i - 1][j];
        else
         dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

    }
}

一维数组

二维数组dp[i][j]=max( dp[i-1][j] ,  dp[i-1][  j-weight[i] ]+value[i] ),都是依照上一行(i-1)行数据进行更新数据,可以直接转化成一维数组,在for循环中一层层更新,根据上一轮的dp[j]进行更新。

这里要注意的是遍历顺序,在遍历书包时,不再是从左往右,而是从右往左。

这是因为dp[j] , dp[  j-weight[i] ]相当于二维数组中的dp[i-1][j] ,  dp[i-1][  j-weight[i] ],都是位于dp[j]的左上方。

如果从左往右遍历,当dp[j]用到是dp[j] ,  dp[  j-weight[i] ]已经发生了更新,此时变成了dp[i][j] ,  dp[i][  j-weight[i] ];因此应该是从右往左遍历,确保用到的dp[j] ,  dp[  j-weight[i] ]还没有发生更新。

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

1049. 最后一块石头的重量 II - 力扣(LeetCode)

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum = 0;
        for (int i = 0; i < stones.size(); i++) {
            sum += stones[i];
        }
        //将题目转化成为两堆质量差不多的子集去抵消,总质量的一半
        bool flag=false;
        if (sum % 2 == 1) {
            flag = true;
        }
        sum = sum / 2;//总质量的一半sum
        int temp = 0;
        //dp数组是从0-i石子中选出满足质量总和是sum,dp(质量)最大,value=weight
        vector<int> dp(sum + 1, 0);
        for (int i = 0; i < stones.size(); i++) {
            for (int j = sum; j >= stones[i]; j--) {
                dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
                temp = dp[j];
                
                }
            }
        
        if (flag == true){
            return sum*2+1 - 2*dp[sum];//总和为奇数时,除以2向下取整,这里+1

        }
        else
            return 2*sum- 2*dp[sum];//题目求解的是两堆的差
    }
};

完全背包

与0-1背包不同的是,这里每个物品能被多次选择。

二维数组

书包容量为j时

不放物品i时,最大价值=dp[i-1][j]     [从(i-1)种物品中去选择的最大价值]

包含物品i时,最大价值=max( dp[i-1][j] ,  dp[ [  j-weight[i] +value[i] )

这里是完全背包的特殊之处,每种物品不止选一次,在选择物品i之前,可能也选择过物品i,所以在留出物品i的容量之后,应该是在i种物品中去选择。

在计算dp[i][j]时,需要用到dp[ [  j-weight[i] ] 位于dp[i][j]的左侧,所以遍历顺序要从左往右。

for (int i = 1; i < n; i++) { // 遍历物品
    for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
        if (j < weight[i]) 
        dp[i][j] = dp[i - 1][j];
        else 
        dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
    }
}

一维

for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量

    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        if (j - weight[i] >= 0) 
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
    
}

例题

518. 零钱兑换 II - 力扣(LeetCode)

class Solution {
public:
    int change(int amount, vector<int>& coins) {
         //初始化一开始所有的dp数组都是0,对于第一排的不用特意赋值,因为dp[j]=dp[j-coins[i]]
        //uint64_t是无符号64位整数,防止溢出

        vector<uint64_t> dp(amount + 1, 0);
        dp[0]=1;
         for(int i=0;i<coins.size();i++){

            for(int j=1;j<=amount;j++){

                if(j>=coins[i]){

                   dp[j]+=dp[j-coins[i]];
                } 
            }
        } 
        return dp[amount]; 
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值