01背包+例题

1.什么是背包问题

背包模型是一个经典的 dp (dynamic programming,动态规划)的模型。 背包问题的描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。

2. 01背包

01背包又是背包问题里面最经典,也是最基础的问题,其他的背包问题都可以由01背包问题转化而来。
01 背包的问题描述为:给定一组物品,每个物品都有各自的体积 和 价值。对于每一个物品,我们只有选和不选 两种方案。要如何选择才能在不超过背包容量的情况下,使总的价值最大。

2.1 问题

N N N 件物品和一个容量是 V V V 的背包。每件物品只能使用一次。

i i i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数, N N N V V V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N N N 行,每行两个整数 v i v_i vi, w i w_i wi,用空格隔开,分别表示第 i i i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围
  • 0 < N , V ≤ 1000 0<N,V≤1000 0<N,V1000
  • 0 < v i , w i ≤ 1000 0< v_i,w_i ≤1000 0<vi,wi1000
输入样例
4 5 
1 2 
2 4 
3 4 
4 5
输出样例:
 8

2.2 问题分析

在这里插入图片描述

  1. 朴素版代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N],v[N],w[N];

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++) scanf("%d%d",&v[i],&w[i]);
    
    for(int i = 1;i <= n;i++){
        for(int j = 0;j <= m;j++){
            //不选第 i 个物品
            f[i][j] = f[i-1][j];
            
            //选第 i 个物品,前提是背包容量 j 要 >= 第 i 个物品的体积 v[i]
            if(j >= v[i])
            f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
        
        /*for(int j = 0;j <= m;j++) cout<<f[i][j]<<" ";
        puts("");*/
    }

    cout<<f[n][m]<<'\n';
    return 0;
}

输出结果为:

0 2 2 2 2 2 
0 2 4 6 6 6 
0 2 4 6 6 8 
0 2 4 6 6 8 
8
  1. 优化版

我们实际上可以优化掉第一维,也就是只使用一维数组来进行求解。定义f(j) 为从前 i 个物品中选,体积不超过j的最大值。

注意如果我们直接将第一维去掉,会出现问题

有问题的代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N],v[N],w[N];

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++) scanf("%d%d",&v[i],&w[i]);
    
    for(int i = 1;i <= n;i++){
        for(int j = 0;j <= m;j++){
            //不选第 i 个物品
            f[j] = f[j];//这一句实际可以去掉
            
            //选第 i 个物品,前提是背包容量 j 要 >= 第 i 个物品的体积 v[i]
            if(j >= v[i])
            f[j] = max(f[j],f[j-v[i]]+w[i]);
        }
        
        for(int j = 0;j <= m;j++) cout<<f[j]<<" ";
        puts("");
    }

    cout<<f[m]<<'\n';
    return 0;
}

错误的结果 :

0 2 4 6 8 10 
0 2 4 6 8 10 
0 2 4 6 8 10 
0 2 4 6 8 10 
10 

问题分析:
在这里插入图片描述
在这里插入图片描述

优化版的代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N],v[N],w[N];

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++) scanf("%d%d",&v[i],&w[i]);
    
    for(int i = 0;i <= n;i++){
        for(int j = m;j >= v[i];j--){

            f[j] = max(f[j],f[j-v[i]]+w[i]);
        }
        
        for(int j = 0;j <= m;j++) cout<<f[j]<<" ";
        puts("");
    }

    cout<<f[m]<<'\n';
    return 0;
}

3. 例题

Leetcode.416分割等和子集
Leetcode.1049最后一块石头的重量 II

3.1 例题1

给你一个 只包含正整数非空 数组 n u m s nums nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:
输入:nums = [1,5,11,5] 
输出:true 
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5] 
输出:false 
解释:数组不能分割成两个元素和相等的子集。
提示:
  • 1 ≤ n u m s . l e n g t h ≤ 200 1 \leq nums.length \leq 200 1nums.length200
  • 1 ≤ n u m s [ i ] ≤ 100 1 \leq nums[i] \leq 100 1nums[i]100

题目分析:

首先求出数组所有元素的和 s u m sum sum。如果 s u m sum sum 是奇数,那么无论如何数组 n u m s nums nums 也不能分为两个相同的子集,直接返回 false 即可。

m = s u m / 2 m = sum / 2 m=sum/2 m m m 就是背包的容量,数组元素既看作 价值 ,又看作 体积。用 01背包dp,看最后能否装满这个容量为 m m m 的背包。如果可以就证明 n u m s nums nums 可以被分割为两个大小相同的子集;否则就不行。

时间复杂度: O ( m × n ) O(m \times n) O(m×n)

C++代码:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        int n = nums.size();
        for(auto &num:nums) sum += num;
        if(sum % 2) return false;

        int m = sum/2;
        bool f[m + 1];
        memset(f,0,sizeof f);
        f[0] = 1;
        for(int i = 0;i < n;i++){
            for(int j = m;j >= nums[i];j--){
                f[j] |= f[j - nums[i]];
            }
        }
        return f[m];
    }
};

JAVA代码:

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        int n = nums.length;
        for(int x:nums) sum += x;
        if(sum % 2 == 1) return false;

        int m = sum / 2;
        boolean[] f = new boolean[m+1];
        f[0] = true;

        for(int i = 0;i < n;i++){
            for(int j = m;j >= nums[i];j--){
                f[j] |= f[j-nums[i]];
            }
        }
        return f[m];
    }
}

3.2 例题2

有一堆石头,用整数数组 s t o n e s stones stones 表示。其中 s t o n e s [ i ] stones[i] stones[i] 表示第 i i i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x x x y y y,且 x ≤ y x \leq y xy。那么粉碎的可能结果如下:

  • 如果 x = y x = y x=y,那么两块石头都会被完全粉碎;
  • 如果 x ≠ y x \neq y x=y,那么重量为 x x x 的石头将会完全粉碎,而重量为 y y y 的石头新重量为 y − x y-x yx

最后,最多只会剩下 一块石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0 0 0

示例 1:
 输入:stones = [2,7,4,1,8,1] 
 输出:1 
 解释: 
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], 
 组合 2 和 1,得到 1,所以数组转化为 [1,1,1], 
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
示例 2:
输入:stones = [31,26,33,21,40] 
输出:5
提示:
  • 1 ≤ s t o n e s . l e n g t h ≤ 30 1 \leq stones.length \leq 30 1stones.length30
  • 1 ≤ s t o n e s [ i ] ≤ 100 1 \leq stones[i] \leq 100 1stones[i]100

题目分析:
在这里插入图片描述
时间复杂度: O ( m × n ) O(m \times n) O(m×n)

C++代码:

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int sum = 0;
        for(auto &num:stones) sum += num;
        int m = sum / 2;
        int f[m+1];
        memset(f,0,sizeof f);

        for(int i = 0;i < stones.size();i++){
            for(int j = m;j >= stones[i];j--){
                f[j] = max(f[j],f[j-stones[i]]+stones[i]);
            }
        }
        return (sum - f[m]) - f[m];
    }
};

Java代码:

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        int n = stones.length;
        for(int x:stones) sum += x;
        int m = sum / 2;
        int[] f = new int[m+1];
        for(int i = 0;i < n;i++){
            for(int j = m;j >= stones[i];j--){
                f[j] = Math.max(f[j],f[j-stones[i]]+stones[i]);
            }
        }
        return (sum - f[m]) - f[m];
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
贪心算法背包问题中的应用是一种常见的方法。背包问题是一个经典的优化问题,目标是在给定的背包容量下,选择一些物品放入背包,使得放入背包的物品总价值最大。 一个简单的贪心算法背包问题例题可以按照以下步骤进行解决: 1. 将所有物品按照单位重量的价值从大到小进行排序。 2. 初始化背包的容量为C,当前背包中放入的物品总价值为0。 3. 从价值最高的物品开始,依次尝试将物品放入背包。 - 如果当前物品的重量小于等于剩余背包容量,则放入背包,并更新当前背包中物品总价值和剩余背包容量。 - 如果当前物品的重量大于剩余背包容量,则不能放入背包,继续尝试下一个物品。 4. 重复步骤3,直到所有物品都被尝试过或者背包容量为0为止。 通过这个贪心算法,可以得到一个近似最优解,即放入背包的物品总价值最大化。但是需要注意的是,这个贪心算法并不能保证一定得到最优解,因为它只考虑了当前的最优选择,并没有全局地考虑所有可能的选择。 这个例题中的贪心算法背包问题可以帮助刚接触贪心算法的小白理解贪心算法的思想和应用。它通过按照单位重量的价值排序,优先选择单位重量价值最高的物品放入背包,从而达到最大化背包中物品总价值的目的。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Greedy Algorithm - 副本 (2).zip](https://download.csdn.net/download/weixin_43817994/12262352)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [贪心算法--背包问题](https://blog.csdn.net/attack_5/article/details/84111786)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [【贪心算法背包问题](https://blog.csdn.net/cqn2bd2b/article/details/128113815)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值