代码编织梦想

1.0-1背包基础

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大

1.1动态规划五部曲

  1. 确定dp数组以及下标的含义:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。在后续的程序中都是默认背包的容量为i,物品的类别为j,保证统一。

2.确定递推公式

  • 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)
  • 放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值

所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

3.初始化:需要根据具体的进行具体的初始化

4.那么问题来了,先遍历 物品还是先遍历背包重量呢?

其实都可以!! 但是先遍历物品更好理解。对于先遍历物品再遍历容量,简称为组合问题:这种情况下的遍历物品的顺序是一个固定的。

// weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); 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 - 1][j - weight[i]] + value[i]);

    }
}

先遍历背包,再遍历物品,也是可以的!(注意我这里使用的二维dp数组)。这种可以称之为排列问题可能会出现相同的结果但是不同的排列方式

例如这样:

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

5.举例:跳过,直接看下面的代码

2.416分割等和子集

第一种方法:动态规划的方法

理论分析:首先要分割成等和的子集,要满足的第一个条件就是其累加和能被2整除。

动态规划五部曲

1.dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]

2.递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);//分为填不添加这个数情况下的最大累加和。二维状态下是:dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i]):i表示物品j表示容量

3.初始化:首先初始化的长度为target+1,因为我们只需要找到一个容量为sum一半的能够全部装满就代表还存在另外一部分。

4.确定遍历顺序:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!因为这样可以避免重复存放。可以看到上面的二维的递推公式,首先遍历的是物品,其次遍历的是容量,如果内部的容量是正序,那么会出现一个物品被多次使用。比如有1 2;2 3(重量 价值)的物品,这样的情况,正序遍历出现的结果就是dp数组为[2,4]而不是[2,3]的情况了。

class Solution {
public:
    //动态规划的方法
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        int n = nums.size();
        for(int i = 0;i<n;i++){
            sum += nums[i];
        }
        if(sum%2 ==1)return false;
        int target = sum/2;
        vector<int> dp(1+target,0);//生成一个dp数组
        for(int i = 0;i<n;i++){
            for(int j = target;j>=nums[i];j--){
                dp[j] = max(dp[j],dp[j-nums[i]]+nums[i]);//分加不加的情况下的最大值
            }
        }
        if(dp[target] == target)return true;
        return false;

    }
};

3.最后一块石头的重量

首先就是分析题意:我该如何让最后的石头的重量最小呢?就是通过将其分成两部分,两部分的差值越小那么对应的最后能够得到的重量也就越小。那么将其化成一个背包问题就是在求容量为target下能够存放的最大的重量为多少即0-1背包问题。

动态规划五部曲

1.dp[j]:容量为j时候的最大价值(重量)。

2.递推公式:dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);//在这个里面重量和价值都是用stones[i]来表示的,所以得到上述的公式,具体可以参考前面这一题的思路。

3.初始化:首先来说的话我们要用sum来确定总重量,然后确定dp数组的空间,应该生成的vector<int> dp(1+sum/2,0)的,但是按照题目给定了最多只有3000个,所以可以直接创建一个大的空间(1500>sum/2)。

4.循环的遍历方向:参照前面的分割等和子集。如果是一维的需要怎样的操作。

注意事项:最后返回的是 return sum - 2*dp[target];因为此时分成的两部分的重量分别为sum -dp[target]和dp[target],且必定是target>=dp[target]所以最后返回的是两者的差值即可。

class Solution {
public:
    //dp数组,求得是能够背得的最大的重量,使用这种方法,巧妙的将碰撞的减法问题给反向思考为一个加法问题,最后相减来确定最小的重量
    int lastStoneWeightII(vector<int>& stones) {
        vector<int> dp(1501,0);
        int sum = accumulate(stones.begin(),stones.end(),0);//求累加和
        int target = sum/2;
        for(int i = 0; i<stones.size();i++){
            for(int j = target;j>=stones[i];j--){
                dp[j] = max(dp[j],dp[j-stones[i]]+stones[i]);
            }
        }
        return sum - 2*dp[target];
    }
};

4.494目标和

结题思路看下面的注释部分。

动态规划五部曲:

1.dp[j]:代表容量为j的最大价值(和)的组合数。

2.递推公式:dp[j] += dp[j-nums[i]];组合类问题就是对应的多个的累加和。

3.初始化:在这个地方初始化需要初始化的长度是dp(avg+1);而不需要sum+1,因为left = avg;

4.遍历顺序:对于组合问题:应该先遍历背包再遍历容器。再就是遍历方向的问题:里面那个是从大往小的进行遍历,同前面一样是为了避免重复的情况。参考分割等和子集问题。

class Solution {
public:
    //在这个里面,left代表的是正数的部分,right代表的是负数的部分
    //left + right = sum(定值)
    //left - right = target(定值)
    //left = (sum + target)/2;//所以此时两者的累加和一定能被2整除才行。
    //还需要考虑的就是sum>=abs(target)不然无法保证能够完成累加到指定的为止
    //所以此时dp[i]表示的就是累加和为i的个数
    //dp[j] += dp[j - nums[i]];//每一个nums[i]都对应的一个dp[j - nums[i]]种方法可以累加到这里
    int findTargetSumWays(vector<int>& nums, int target) {
        int n = nums.size();
        
        int sum = accumulate(nums.begin(),nums.end(),0);//求和,后面那个应该是初始化的赋值
        if(abs(target)>sum)return 0;
        if((target+sum)%2==1) return 0;
        int avg = (target+sum)/2;
        vector<int> dp(avg+1);
        dp[0] = 1;
        for(int i = 0;i<nums.size();i++){
            for(int j = avg;j>= nums[i];j--){
                dp[j] += dp[j-nums[i]];
            }
        }
        return dp[avg];
    }
};

5.474一和零

1.dp[i][j]:i表示0的个数,j表示1的个数。dp[i][j]表示当前容量下的最大的个数。

2.递推公式: dp[i][j] = max(dp[i][j],dp[i-zeros][j-ones]+1);//其中dp[i-zeros][j-ones]表示的是dp[j-weight[i]]的意思,1:表示value[i].

3.初始化:默认开始的时候都是0。

4.遍历顺序:反序遍历保证不重复。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));//生成一个dp数组,用于确定有i个1,j个0
        for(auto str:strs){
            int zeros = 0;
            int ones = 0;
            for(auto st:str){
                if(st == '0'){
                    zeros++;
                }else{
                    ones++;
                }
            }
            for(int i = m;i >=zeros;i--){
                for(int j = n;j>=ones;j--){
                    dp[i][j] = max(dp[i][j],dp[i-zeros][j-ones]+1);
                }
            }
        }
        return dp[m][n];
    }
};

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/mooc666quq/article/details/138718324

力扣阶段练习(1).消失的数字-爱代码爱编程

引言         以自述式的笔记展示,尽可能用最好理解的方式去叙述我对知识点的理解,方便有需求的小伙伴查看理解,同时锻炼自身的表达能力,共同学习,共同进步,争取“双赢”!         注:本文章根据自身学习的笔记和自身理解编写,难免存在纰漏或不足之处,如有不足,恳请各位在评论区批评指正,谢谢! 一、题目描述       此题链接:面试题 1

【力扣一轮】数组-爱代码爱编程

旋转矩阵 题目链接 随想录链接 旋转的方式把数字一个接一个写入矩阵中。 这时需要用到循环不变量,它是一个循环,每次处理的时候需要处理的方式是一样的,而不是有着条件的限制。 对于这道题,循环不变量就是边元素的处理,只

【算法】noip2003神经网络-爱代码爱编程

题目描述 人工神经网络(Artificial Neural Network)是一种新兴的具有自我学习能力的计算系统,在模式识别、函数逼近及贷款风险评估等诸多领域有广泛的应用。对神经网络的研究一直是当今的热门方向,兰兰同学在自

嵌入式学习第三十五天!(算法)-爱代码爱编程

算法:解决特定问题求解步骤 1. 算法的设计:     1. 正确性:语法正确;合法的输入能得到合理的结果;对非法的输入,给出满足要求的规格说明;对精心选择,甚至刁难的测试都能正常运行,结果正确;     2. 可读性:便于交流,阅读,理解(高内聚,低耦合);     3. 健壮性:输入非法数据,能进行相应的处理,而不是产生异常;     4.

c语言笔记14-爱代码爱编程

指针1         在C语言中给内存单元的编号起了个名字叫做指针,通俗来说就是地址。(内存单元编号=地址=指针) 1.指针变量与地址 int a=10;  int* p=&a;   *  说明了这里p的是指针变量;int*说明p是一个整形指针;int说明p指向的对象a是一个整形;&a是取出a的地址; 总的来说就是取出a的地址存

atcoder beginner contest 353-爱代码爱编程

A int n, a[1000]; void solve(){ cin >> n; for(int i = 1; i <= n; i ++){ cin >>

技艺高超的魔法师:java运算符-爱代码爱编程

在Java编程的世界里,运算符是连接变量和表达式的关键纽带,它们使得程序能够执行计算、比较、赋值等一系列操作。 一,基本概念 1,运算符是什么? 运算符是操作变量的符号。 2,分类 Java中的主要运算符类型:

cow exhibition g的来龙去脉-爱代码爱编程

[USACO03FALL] Cow Exhibition G - 洛谷 曲折经过 爆搜 一开始没什么好的想法,就针对每头奶牛去or不去进行了爆搜。 #include <cstdio> #include <algorithm> using namespace std; #define maxn 405 int iq[maxn

【算法】二分算法——x的平方根-爱代码爱编程

本节博客使用“二分边界算法”对“x的平方根”这一题目进行求解,有需要借鉴即可。 目录 1.题目2.暴力求解3.二分算法求解4.取值范围细节5.总结 1.题目 题目链接:LINK 2.

c动态内存管理-爱代码爱编程

malloc calloc realloc free 原则:谁申请,谁释放,防止内存泄漏 #include<stdio.h> #include<stdlib.h> int mian() { i

leetcode 669. 修剪二叉搜索树-爱代码爱编程

LeetCode 669. 修剪二叉搜索树 1、题目 题目链接:669. 修剪二叉搜索树 给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low,

基于mswa相继加权平均的交通流量分配算法matlab仿真-爱代码爱编程

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述       基于MSWA相继加权平均的交通流量分配算法matlab仿真.如图所示交通网络中,包含6个节点、11各路段、9个OD对。经枚举可得每个OD对间存在3条无折返有效路径,共27条。 2.测试软件版本以及运

day24 235. 二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点-爱代码爱编程

235. 二叉搜索树的最近公共祖先   利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。 因为是有序树,所以 如果 中间节点是 q 和 p 的公共祖先,那么 中节点的数组 一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q &