算法题:求所需的最小的书包数量(拓展拓展再拓展~)

算法题:求所需的最小的书包数量

现在有一种书包,这种书包只有两个书槽(即最多只能放下两本书。),并且一个这种书包只能装下N千克的书。现在有一个数组,数组元素是每本书的重量(千克)。问:完全装下这堆书,需要多少个书包?

题解如下:

正向暴力法

解析:

  • 首先,很容易能想到的一个解法就是暴力解法。

    贪心思想:我们先让两本最重的书先结合!

    1. 先倒序。

    2. 让最重的那个与后面的所有书逐个比较,看看它能与后面的哪本书结合,放到一个书包里面。

    (这样就能够保证最重的和较重的先被放进书包,后面轻的就更不在话下了!)

 
class Solution1 {
public:int GetBagNum(int bagLimit, vector<int> &books) {sort(books.begin(), books.end(), greater<>());vector<bool> inBags(books.size(), false); // 表示当前的书是否被装入书包了。int bagNum = 0;for (int i = books.size() - 1; i >= 0; i--) {if (inBags[i]) {    // 表示已经被装入书包continue;}for (int j = i - 1; j >= 0; j --) {if (!inBags[j] && books[i] + books[j] <= bagLimit) {    // 表示 i 和 j 可以装入同一个书包inBags[i] = true;inBags[j] = true;bagNum ++;break;}}if (!inBags[i]) { // 表示无法与后面的所有书一起放进书包,只能自己单独放bagNum ++;}}return bagNum;}
};

明显,出现O(n²)的算法是无法跑过所有用例的。

所以,我们要想出个办法,如何才能化简掉没有必要的比较操作呢?

仔细一看,还真的难想出来。因为感觉每一次比较都是必要的,要不然没法让较大的两本书放进书包。

从开头想,没办法化简,那就从尾想

从开头想(正向):正是上面的正向暴力法(让两个大的先放进去)

从末尾想(方向):让最大的跟最小的先放进去。

双指针法

  • 让最大的和最小的结合。(因为对最小的来说,最极限的情况就是和最大相结合。也就说,对最小的书来说,它非常贪心,老是想和最大的书结合放进书包。)

方法:

  1. 先倒序;

  2. 左指针向右遍历,右指针向左遍历。

class Solution2 {
public:int GetBagNum(int bagLimit, vector<int> &books) {sort(books.begin(), books.end(), greater<>());int left = 0;int right = books.size() - 1;int bagNum = 0;while (left < right) {if (books[left] + books[right] <= bagLimit) {// 这两本放到一起bagNum ++;left ++;right --;} else {// 左边的那本单独放left ++;bagNum ++;}}return bagNum;}
};

这样,就将时间复杂度降到了O(n)。可以通过所有用例了!

思维拓展:

假如题目变种为:

现在有一种书包,这种书包只有两个槽位(即最多只能放下两件物品。),并且一个这种书包只能装下N千克的物品。现在有两种物品,一种物品A,一种物品B。一个书包内【不能放两个物品A】,但是【可以放一个A、一个B】或者【直接放两个物品B】。给你两个数组,数组1为物品A的重量数组;数组2为物品B的重量数组。数组元素是每个物品的重量(千克)。问:完全装下这堆物品,需要多少个书包?

明显这道题也是双指针就能解决。

class Solution3 {
public:int GetBagNum(int bagLimit, vector<int> &booksA, vector<int> &booksB) {sort(booksA.begin(), booksA.end()); // 升序排列sort(booksB.begin(), booksB.end(), greater<>()); // 降序排列
​// 对于轻的A来说,它的贪心就是和最大的B结合放进书包int leftA = 0;int leftB = 0;int bagNum = 0;vector<bool> BInBag(booksB.size(), false); // 表示B物品是否被装进书包while (leftA < booksA.size() && leftB < booksB.size()) {if (booksA[leftA] + booksB[leftB] <= bagLimit) {BInBag[leftB] = true;bagNum ++;leftA ++;leftB ++;} else {leftB ++;}}bagNum += (booksA.size() - leftA); // 剩余的A物品是没法和物品B的结合的,只能单独装入书包。// 以下让剩余没装入的B使用双指针继续装入。int left = 0;int right = booksB.size() - 1;while (left < right) {if (booksB[left]) { // 跳过已经被装入的left ++;   continue;} if (booksB[right]) {    // 跳过已经被装入的right --;continue;}if (booksB[left] + booksB[right] <= bagLimit) {// 这两个放到一起bagNum ++;left ++;right --;} else {// 左边的那个单独放left ++;bagNum ++;}}
​return bagNum;}
};

贪心+小根堆

假如我们还是坚持要用原来那种贪心:总是让两个大的先结合。

从头想过了,无法化简;从尾想过了,还好,时间复杂度下降了;还有一个地方,可以从中间想

我们再来继续仔细看看这张图,对于11来说,它既得跟17相加比较一把,还得跟13相加比较一把。有没有一种可能?让11直接跟13相加比较一把,发现不行——> 那么可以直接得出结论:11跟17是没法结合的。(因为17比13大)

也就是说,我们可以把17和13这些比较大的书转到一个容器里面,让容器里面最小的跟11相加比较,如果可以,那就装入书包;如果不行,11和容器里面的每一个都不能相结合(因为11已经和容器里面最小的相结合比较过了)

  • 可以获取到最小元素的容器,那就只有小根堆了

但是我们要怎么把书装进小根堆呢?(什么情况装进去?)

  • 很明显,当我们遍历到一本书,并且此书没法和堆顶元素相结合的时候,就要将其加进小根堆了。(加进小根堆,看看后续的遍历元素有没有可能再和当前的书)

class Solution {
public:int GetBagNum(int bagLimit, vector<int> &books) {sort(books.begin(), books.end(), greater<>());
​priority_queue<int, vector<int>, greater<>> bigBook;
​int ret = 0;
​for (auto &cur: books) {if (bigBook.empty() || bigBook.top() + cur > bagLimit) {    // 没法和最小的堆顶元素结合,那就加入堆。bigBook.push(cur);} else {bigBook.pop();  // 如果可以和堆顶元素结合,那就将堆顶元素弹出来让其与当前元素结合。ret ++;}}return ret + bigBook.size();    // 剩余在堆里面的,都是没法和其他元素结合的。}
};
  • 一个疑问:为什么和堆顶元素结合就直接结合了?为什么不和堆里面的其他元素再比较一下,看看有没有可能和更大的元素结合?

    答案:没必要,数组后面还有更小的元素,更小的元素会和堆内其他元素结合的。

思维拓展:

假如题目变成这样:

现在有一种书包,这种书包只有3个书槽(即最多只能放下3本书。),并且一个这种书包只能装下N千克的书。现在有一个数组,数组元素是每本书的重量(千克)。问:完全装下这堆书,需要多少个书包?

题目如下,核心思想是一致的。都是先跟大的里面的小的比。(通过维护两个堆来完成这一功能)

class Solution4 {
public:int GetBagNum(int bagLimit, vector<int> &books) {sort(books.begin(), books.end(), greater<>());
​priority_queue<int, vector<int>, greater<>> pq1Book;    // 代表只有1本书的小根堆
​priority_queue<int, vector<int>, greater<>> pq2Book;    // 代表2本书的和的小根堆
​int ret = 0;
​for (auto &cur: books) {if (pq2Book.empty()) {if (pq1Book.empty()) {pq1Book.push(cur);} else {if (cur + pq1Book.top() <= bagLimit) {// 也就是说这两本书能够结合,能结合就放到堆2放着。int cur_top = pq1Book.top();pq1Book.pop();int sum = cur_top + cur;pq2Book.push(sum);} else {// 如果不能结合,就分开,都放到堆1pq1Book.push(cur);}}} else {if (cur + pq2Book.top() <= bagLimit) { // 直接和堆2顶的两本书结合。ret ++;pq2Book.pop();} else {    // 没法和堆2顶的任意两本书结合,只能到堆1碰碰运气if (cur + pq1Book.top() <= bagLimit) {// 也就是说这两本书能够结合,能结合就放到堆2放着。int cur_top = pq1Book.top();pq1Book.pop();int sum = cur_top + cur;pq2Book.push(sum);} else {// 如果不能结合,就分开,放到堆1pq1Book.push(cur);}}}}// 剩余的两个堆里面的书都是没法相互结合的。前面已经判断过了。return ret + pq2Book.size() + pq1Book.size();}
};

总结

贪心就是排序!

贪心就是排序!

贪心就是排序!

将排序用好,就能解决所有的贪心问题!

此处的排序不仅仅是sort!还包括堆priority_queue、红黑树map这两个有序数据结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/238256.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMQ学习一

RabbitMQ学习 RabbitMQ相关概念Virtual host数据隔离SpringAMQP第一种 基本消息模型第二种 WorkQueues模型第三种 发布订阅模型&#xff08;fanout交换机&#xff09;fanout交换机实例 第四种 Direct交换机direct交换机实例基于注解的方式声明——direct交换机 第五种Topic交换…

从订阅式需求发展,透视凌雄科技DaaS模式增长潜力

订阅制&#xff0c;C端消费者早已耳熟能详&#xff0c;如今也凭借灵活、服务更新稳定的特点&#xff0c;逐渐成为B端企业服务的新热点。 比如对中小企业而言&#xff0c;办公IT设备等配套支出都必不可少&#xff0c;但收入本身并不稳定&#xff0c;购置大堆固定资产&#xff0…

Shell循环:for(三)

示例&#xff1a;使用for实现批量主机root密码的修改 一、前提 已完成密钥登录配置&#xff08;ssh-keygen&#xff09;定义主机地址列表并了解远程修改密码的方法 [rootlocalhost ~]# ssh-keygen #设置免密登录[rootlocalhost ~]# ssh-copy-id 192.168.151.151 二、演示…

【Linux系统化学习】揭秘 命令行参数 | 环境变量

个人主页点击直达&#xff1a;小白不是程序媛 Linux专栏&#xff1a;Linux系统化学习 代码仓库&#xff1a;Gitee 目录 命令行参数 环境变量 PATH 查看PATH $PWD 查看环境变量PWD $HOME 查看系统支持的环境变量 获取环境变量 命令行参数 在C/C编程语言中我们有一个…

用element-ui进行简单的商品管理

安装element-ui 项目的控制台输入npm i element-ui -S main.js import ElementUI from element-ui;//引入element-ui模块 import element-ui/lib/theme-chalk/index.css;//引入element-ui的css样式 Vue.use(ElementUI);//使用ElementUI 商品管理组件 <template><…

8、Qt中定时器的使用

一、说明 在Qt中常使用如下两种定时器 1、使用QObiect类的定时器事件QTimerEvent 与定时器相关的函数有&#xff1a;startTimer()、timeEvent()、killTimer()&#xff1b;startTimer(int interval)函数开始一个定时器并返回定时器ID&#xff0c;如果不能开始一个定时器&…

家政服务预约小程序系统的开发;

家政服务预约小程序系统的开发&#xff0c;既是对传统加盟服务模式的创新&#xff0c;也是家政商家企业营销推广服务的升级。它推动整个家政服务行业实现线上线下深度融合&#xff0c;提升用户消费体验&#xff0c;实现了雇主、服务提供者、家政企业商家三者之间的无缝衔接&…

go开发之个微机器人的二次开发

简要描述&#xff1a; 下载消息中的语音 请求URL&#xff1a; http://域名地址/getMsgVoice 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型…

【Node.js后端架构:MVC模式】基于expres讲解

Node.js后端架构&#xff1a;MVC模式 什么是MVC MVC (Model-View-Controller) 是一种软件设计模式&#xff0c;用于将应用程序的逻辑分离成三个不同的组件&#xff1a;模型、视图和控制器。 模型&#xff08;Model&#xff09;负责处理应用程序的数据逻辑。它负责从数据库或其…

详解Linux常用命令

目录 1. ps 命令 2. top 命令 3. grep 命令 4. df 命令 5. tail 命令 6. head 命令 7. cat 命令 8. --help 和 man 命令 9. cd 命令 10. mkdir 命令 11. rm 命令 12. mv 和 cp 命令 13. touch 命令 14. vi 或 vim 命令 15. chmod 修改权限 16. 打包和压缩文件 …

Linux 权限管理

1 Linux 安全模型 AAA认证资源分派&#xff1a; 当用户登录时&#xff0c;系统会自动分配令牌 token&#xff0c;包括用户标识和组成员等等信息 1.1 用户 Linux 中每个用户是通过 User ID&#xff08;UID&#xff09;来唯一标识的。 1.2 用户组 Linux 中可以将一个或者多个…

如何通过添加香港高防IP来防御攻击?

​  针对外贸建站&#xff0c;租用香港服务器&#xff0c;除了站长们较为关注的价格外&#xff0c;安全性也是至关重要的。香港服务器在使用中可能会遭受到常见的 DDoS 网络攻击&#xff0c;而在 DDoS 防护这一块&#xff0c;您可以使用香港 DDoS 高防 IP 和香港高防服务器来…