Day 42 动态规划 4

这篇具有很好参考价值的文章主要介绍了Day 42 动态规划 4。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

K46. 背包理论基础(二维背包)

代码随想录

1. 思路

背包问题的主要特征为,在有限制的情况下满足最优化,因此可以构造二维dp数组,一个维度记录成本,一个维度记录收益,一步步寻找最优解。

(1)dp数组以及下标含义

dp[i][j]代表0-i的物品,在j的背包容量下,可以形成的最大价值。注意,这里i为序数,第一个第二个物品这样,而j为基数,也就是对应着成本的单位,比如kg。因此,如果有3个物品,成本分别为1、3、5kg,则i取0-2,j取0-5。

(2)确定递推公式

每次更新都有两个可选择的方式,一种是放入这个物品,一种是不放入。如果放入,则放入前背包中的物品个数位i-1,最大容量为j-weight[i],放入后价值+value[i]。如果不放入,则背包保持之前的状态,有i-1件物品,j的最大重量。我们需要选取其中更大的,因此表达式为:

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

注意,如果j<weight[i],说明第i个物品根本放不进背包,把其他的东西都腾出来也没戏,所以只有一种可能性,就是退回到上一步,也就是dp[i-1][j]。

(3)如何初始化

因为每一步更新涉及左上的元素,因此第一行和第一列必须要初始化。第一列代表重量限制为0的情况下总价值,因为放不了任何东西,所以全部初始化为0。

第一行代表如果只放第一个物品,随着背包重量限制的提升,总价值是什么。不难想到,这一行应该是一个阶梯函数,取值为0和value[i],也就是最大限制下放和不放这个物品的价值。

(4)遍历顺序

有两种遍历顺序,这里都详细讲一下。

按列遍历。这种情况相当于在给定不同背包容量限制下,不断往里放东西,最大的承载。按理说依次往里面放就可以,但是还真不是有大的就放就好,因为它会因为dp[i-1][j-weight[i]]+value[i](dp更新的第二项)挤占之前的空间,如果小的更有性价比,还不如不放。

按行遍历。这种情况相当于在给定物品的情况下,扩大背包容量限制,看能放多少。按理说应该就是这个物品本身,但是会有小成本的加塞,可能让总价值更大,表达为dp[i-1][j](dp更新的第一项),如果小的更有性价比,等到了可以堆叠的时候也不必了。

可以看出,不论如何遍历,都能做到最优化,让性价比达到最优。

(5)实例展示

在展示实例的时候,最核心的就是更小的物品可能更有性价比这个思想。比如,一个3成本的价值20,一个1成本的价值15,后者就更有性价比。

void test_2_wei_bag_problem1() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;

    // 二维数组
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 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]) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

        }
    }

    cout << dp[weight.size() - 1][bagweight] << endl;
}

int main() {
    test_2_wei_bag_problem1();
}

K46. 背包理论基础(一维背包)

代码随想录

1. 思路

滚动数组的核心逻辑是讲之前的二维数组进行压缩。而之所以可以压缩,是因为一些环节出现了冗余,换句话说,删除这些冗余信息后,并不会威胁到需要保留的信息

(1)从数学角度进行压缩

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

a. 在i这个维度上下一个状态只依赖于i-1这个状态,所以这个维度可以压缩。只需要保证在遍历每个i的时候,将dp[i]更新为dp[i]即可。这也是为什么很多一维动态规划的题目可以压缩成一两个元素不断轮换

b. 在j这个维度上,因为下一个状态依赖于j-weight[i]这个状态,所以不太好确定,因此需要保留这个维度。同时,因为在更新j的时候,需要j-weight[i]保持上一轮的状态,因此需要从后向前更新,不然应该保留的信息会被改变。

这样问题就明朗了,我们只需要保存j这一个维度,代表背包的容量,从最大容量到最小容量遍历,每一遍代表一个物品。

(2)实际含义的解释

a. 目前的dp数组表示在j这个容量下最大的价值,至于是多少个物品的,得看处于第几轮。每一轮更新一个物品,第i轮第j个元素表示0-i物品放到j这个限制的背包中,所获得的最大价值。

b. 更新的方程为:dp[j] = max(dp[j], dp[j-weight[i]]+value[i])

c. 初始化的时候,全部填充为0。这里涉及到第0轮和第1轮的含义,我们可以让第1轮表示放第一个物品,而第0轮则代表不放物品,这样第一轮就代表二维背包中第一行的初始化,也就是放1个物品可以获得的最大价值。

d. 遍历顺序为,每个物品一轮,每一轮从右向左遍历

2. 实现

由于在j<weights[i]时,j保持不变,因此在这里体现为dp[j] = dp[j],因此在for循环的时候,可以书写成 for(int j = bagWeight; j >= weight[i]; j--)

此外,注意更新条件中i和j的区别,不要弄混。

void test_1_wei_bag_problem() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;

    // 初始化
    vector<int> dp(bagWeight + 1, 0);
    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]);
        }
    }
    cout << dp[bagWeight] << endl;
}

int main() {
    test_1_wei_bag_problem();
}

416. 分割等和子集

代码随想录

1. 思路

这道题可以转化为有没有n个数加起来等于sum/2,这样就转化为了价值和成本都为成本的01背包问题。

只有确定了如下三点,才能把背包问题套到本题上来:

  • 背包的限制:背包的体积为sum/2,背包如果正好装满,说明找到了总和为 sum/2 的子集。
  • 成本和价值:背包要放入的商品(集合里的元素)重量为元素的数值,价值也为元素的数值
  • 是否可以重复:背包中每一个元素是不可重复放入。(一个商品如果可以重复多次放入是完全背包,而只能放入一次是01背包,写法还是不一样的)

这道2. 实现

由于

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;

        // dp[i]中的i表示背包内总和
        // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001, 0);
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
        }
        // 也可以使用库函数一步求和
        // int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2 == 1) return false;
        int target = sum / 2;

        // 开始 01背包
        for(int i = 0; i < nums.size(); i++) {
            for(int j = target; j >= nums[i]; j--) { // 每一个元素一定是不可重复放入,所以从大到小遍历
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        // 集合中的元素正好可以凑成总和target
        if (dp[target] == target) return true;
        return false;
    }
};

 文章来源地址https://www.toymoban.com/news/detail-793573.html

到了这里,关于Day 42 动态规划 4的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯

    斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: 给定 n ,请计算 F(n) 。 示例 1: 示例 2: 示例 3: 提示: 0 = n = 30 思路:动规五步 确定dp数组和数组下标含义 DP题目都需要定义一维

    2024年02月13日
    浏览(57)
  • 【Day42】代码随想录之动态规划0-1背包_416. 分割等和子集

    动态规划理论基础 动规五部曲: 确定dp数组 下标及dp[i] 的含义。 递推公式:比如斐波那契数列 dp[i] = dp[i-1] + dp[i-2]。 初始化dp数组。 确定遍历顺序:从前到后or其他。 推导dp数组。 出现结果不正确: 打印dp日志和自己想的一样:递推公式、初始化或者遍历顺序出错。 打印

    2024年02月20日
    浏览(68)
  • Day42|动态规划part04: 01背包问题,你该了解这些!、滚动数组、416. 分割等和子集

    其他背包,面试几乎不会问,都是竞赛级别的了,leetcode上连多重背包的题目都没有,所以题库也告诉我们,01背包和完全背包就够用了。 而完全背包又是也是01背包稍作变化而来,即:完全背包的物品数量是无限的。 01 背包问题描述 有n件物品和一个最多能背重量为w 的背包

    2024年04月25日
    浏览(38)
  • 数据结构与算法之美学习笔记:42 | 动态规划实战:如何实现搜索引擎中的拼写纠错功能?

    本节课程思维导图: 利用 Trie 树,可以实现搜索引擎的提示功能,这样可以节省用户输入搜索的时间。实际上,搜索引擎在用户体验方面的优化还有很多,比如你可能经常会用的拼写纠错功能。 当你在搜索框中,一不小心输错单词时,搜索引擎会非常智能地检

    2024年02月03日
    浏览(61)
  • 算法记录 | Day53 动态规划

    思路: 本题和动态规划:718. 最长重复子数组 (opens new window)区别在于这里不要求是连续的了,但要有相对顺序,即:“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。 1.确定dp数组(dp table)以及下标的含义 dp[i][j] :长度为[0, i - 1]的字符串text1与长度为[0,

    2024年02月03日
    浏览(49)
  • 算法记录 | Day46 动态规划

    思路: 1.确定dp数组以及下标的含义 dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词 。 2.确定递推公式 如果 s[0: j] 可以拆分为单词(即 dp[j] == True ),并且字符串 s[j: i] 出现在字典中,则 dp[i] = True 。 如果 s[0: j] 不可以拆分为单词(即

    2024年02月02日
    浏览(39)
  • 算法|Day46 动态规划14

    LeetCode 1143- 最长公共子序列 题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 题目描述 :给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一个新的字符串:它是由原

    2024年02月11日
    浏览(45)
  • 算法记录 | Day38 动态规划

    对于动态规划问题,将拆解为如下五步曲 确定dp数组(dp table)以及下标的含义 确定递推公式 dp数组如何初始化 确定遍历顺序 举例推导dp数组 思路: 确定dp数组(dp table)以及下标的含义:dp[i]的定义为:第i个数的斐波那契数值是dp[i] 确定递推公式:状态转移方程 dp[i] = dp

    2023年04月22日
    浏览(44)
  • 算法|Day40 动态规划9

    LeetCode 198- 打家劫舍 题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 题目描述 :你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统, 如果两间相邻的房屋在同一晚上

    2024年02月12日
    浏览(30)
  • 算法记录 | Day55 动态规划

    思路: 1.确定dp数组(dp table)以及下标的含义: dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为 dp[i][j] 。 2.确定递推公式: if (s[i - 1] == t[j - 1]) t中找到了一个字符在s中也出现了, dp[i][j] = dp[i - 1][j - 1] + 1 if (s[i - 1] != t[j - 1]) 相当于t要

    2024年02月03日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包