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

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

现在有一种书包,这种书包只有两个书槽(即最多只能放下两本书。),并且一个这种书包只能装下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 {
                        // 如果不能结合,就分开,都放到堆1
                        pq1Book.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 {
                        // 如果不能结合,就分开,放到堆1
                        pq1Book.push(cur);
                    }
                }
            }
        }
        // 剩余的两个堆里面的书都是没法相互结合的。前面已经判断过了。
        return ret + pq2Book.size() + pq1Book.size();
    }
};

总结

贪心就是排序!

贪心就是排序!

贪心就是排序!

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript基础—对象、内置对象、Math、案例分析—随机点名、猜数字游戏、生成随机颜色、网页页面渲染、堆栈

版本说明 当前版本号[20231201]。 版本修改说明20231201初版 目录 文章目录 版本说明目录JavaScript 基础 - 第5天对象语法属性和访问方法和调用null遍历对象 内置对象Math属性方法 案例分析案例一 随机点名案例二 随机点名改进案例三 猜数字游戏案例四 生成随机颜色案例五 …

使用WalletConnect Web3Modal v3 链接钱包基础教程

我使用的是vueethers 官方文档&#xff1a;WalletConnect 1.安装 yarn add web3modal/ethers ethers 或者 npm install web3modal/ethers ethers2.引用 新建一个js文件&#xff0c;在main.js中引入&#xff0c;初始化配置sdk import {createWeb3Modal,defaultConfig, } from…

【QuickSort】单边快排思路及实现

思路&#xff1a; &#xff08;1&#xff09;首先定义一个递归函数&#xff1a;qucikSort(int [ ] arr,int l,int r)。函数的定义&#xff1a;给定一个数组arr&#xff0c;对它在[l,r]这个区间内的元素进行排序&#xff0c;从而使得整个数组在[l,r]这个区间内有序。 &#xff0…

RFNet

表1 复现的平均值–Complete&#xff1a;79.218894&#xff0c;Core&#xff1a;73.4977&#xff0c;Enhancing&#xff1a;58.45406 不如论文结果&#xff0c;但在10个点内&#xff0c;还能接受 表4 复现结果–Complete&#xff1a;54.09826&#xff0c;Core&#xff1a;55.3…

手敲单链表,简单了解其运行逻辑

1. 链表 1.1 结构组成 链表是一种物理存储结构上非连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 链表的结构如下图所示&#xff0c;是由很多个节点相互通过引用来连接而成的&#xff1b;每一个节点由两部分组成&#xff0c;分别数据域&…

LeetCode 1205 每月交易2(PostgreSQL)

数据准备 create type state as enum(approved, declined); create table Transactions( id int, country varchar(4), state_enum state, amount int, trans_date date ); Create table If Not Exists Chargebacks ( trans_id int, trans_date date ); insert into Transac…

对小程序的初了解

WXML和HTML的区别 标签名称不同 HTML&#xff1a;div、a、span、img WXML&#xff1a;view、text、image、navigator 属性节点不同 <a href"#">超链接</a> <navigator url"/pages/home/home"></navigator> 提供了类似vue的…

HTTP协议、Java前后端交互、Servlet

文章目录 抓包工具 FiddlerHTTP 请求和响应结构URL 唯一资源定位符HTTP 协议中的方法请求报头&#xff08;header&#xff09;HTTP响应构造 HTTP 请求基于 form 标签基于 ajax使用 Postman HTTPS和 HTTP 的区别对称密钥和非对称密钥数字证书 TomcatServlet创建 Maven 项目引入依…

Linux基础项目开发1:量产工具——文字系统(四)

前言&#xff1a; 前面我们已经把显示系统&#xff0c;输入系统的框架搭建好了&#xff0c;那么有了输入和显示&#xff0c;显示的内容应该是什么呢&#xff1f;这节就要让我们一起对显示的内容&#xff0c;文字系统进行搭建。 目录 一、数据结构抽象 1.描述一个文字的位图&a…

西南科技大学(数据结构A)期末自测练习三

一、填空题&#xff08;每空1分&#xff0c;共10分&#xff09; 1、为解决计算机主机与打印机之间速度不匹配的问题&#xff0c;通常设置一个打印数据缓冲区。主机将要输出的数据依次写入缓冲区&#xff0c;打印机则依次从缓冲区中取出数据&#xff0c;则该换缓冲区的逻辑结构…

JIRA 基本使用

该页面可以&#xff1a; 查看个人基本信息以及归属的邮件组修改常用参数配置查看指给自己的 Open 问题查看自己最近的活动记录等 权限管理 Project 权限管理 JIRA 项目有三种通用权限方案&#xff1a; 公开权限方案&#xff08;默认禁止使用此方案&#xff09;&#xff1a…

FlowJo软件的简单介绍 掌控流式细胞分析的科技巨匠 FlowJo10

FlowJo 10 for Mac是一款强大的流式细胞数据分析软件&#xff0c;具有以下功能&#xff1a; 数据导入与预处理&#xff1a;FlowJo 10可以轻松导入各种类型的流式细胞数据&#xff0c;并对数据进行预处理&#xff0c;包括去噪、背景校正等&#xff0c;以确保数据的准确性和可靠…

视频怎么去水印?如何下载保存无水印视频?

你是否曾经在观看鬼畜素材视频时&#xff0c;被烦人的水印挡住了视线&#xff0c;让你感到十分郁闷&#xff1f;不要担心&#xff0c;今天我将为你介绍几种经典的方法&#xff0c;让你轻松下载无水印视频&#xff0c;让观看体验更加清爽不留痕迹。让我们一起来试试吧&#xff0…

vue实现数字千分位格式化 如6,383,993,037,937.463

1.封装文件&#xff1a;numberToCurrency.js /**实现数字千分位格式化 如6,383,993,037,937.463 */ export function numberToCurrencyNo(value) {if (!value) return 0// 获取整数部分const intPart Math.trunc(value)// 整数部分处理&#xff0c;增加,const intPartFormat …

牛客算法题 【HJ97 记负均正】 golang实现

题目 HJ97 记负均正 描述 首先输入要输入的整数个数n&#xff0c;然后输入n个整数。输出为n个整数中负数的个数&#xff0c;和所有正整数的平均值&#xff0c;结果保留一位小数。 0即不是正整数&#xff0c;也不是负数&#xff0c;不计入计算。如果没有正数&#xff0c;则平均…

如何访问电脑的组策略编辑器?

如何打开组策略 如果我们使用的是 Win 10 系统&#xff0c;如何打开组策略&#xff1f;下面为大家总结了四种打开组策略编辑器的方法。 从搜索框打开 Win 10 策略组怎么打开&#xff1f;一个简单快速的方法就是使用 Windows 自带的搜索栏。我们可以向搜索框中输入“编辑组策…

netty源码:(1)NioEventLoopGroup

EventLoopGroup bossGroup new NioEventLoopGroup(); 不加参数创建NioEventLoopGroup的话&#xff0c;会使用cpu核数*2作为bossGroup的线程数。

2023年阅读类APP如何发展?怎么做好商业化? | TopOn观察

前言 阅读类APP作为泛娱乐应用的重要板块&#xff0c;近年来在全球都发展火热。本文将主要从阅读类应用的市场规模、头部产品及地区特点、商业化模式及提升商业变现几个方面入手&#xff0c;解析2023年阅读类APP的发展趋势&#xff0c;希望为阅读类应用开发者带来参考价值。 一…

使用visual Studio MFC 平台实现对灰度图添加椒盐噪声,并进行均值滤波与中值滤波

平滑处理–滤波 本文使用visual Studio MFC 平台实现对灰度图添加椒盐噪声&#xff0c;并进行均值滤波与中值滤波 关于其他MFC单文档工程可参考 01-Visual Studio 使用MFC 单文档工程绘制单一颜色直线和绘制渐变颜色的直线 02-visual Studio MFC 绘制单一颜色三角形、渐变颜色边…

利用python连接MySQL数据库并执行相关sql操作

一、新建MySQL数据库 1.启动MySQL服务 打开phpstudy&#xff0c;开启MySQL服务。如果开启失败的话&#xff0c;可以打开任务管理器&#xff0c;把正在运行的mysqld服务的进程进行关闭&#xff0c;再次打开MySQL服务即可启动。 2.新建MySQL数据库 选择数据库&#xff0c;点击…