【leetcode 2171. 拿出最少数目的魔法豆】没有数学,全是思路

2171. 拿出最少数目的魔法豆

题目描述

给定一个 正整数 数组 beans ,其中每个整数表示一个袋子里装的魔法豆的数目。

请你从每个袋子中 拿出 一些豆子(也可以 不拿出),使得剩下的 非空 袋子中(即 至少还有一颗 魔法豆的袋子)魔法豆的数目 相等。一旦把魔法豆从袋子中取出,你不能再将它放到任何袋子中。

请返回你需要拿出魔法豆的 最少数目

如果读者已经看了官方题解,会发现是通过一系列数学推理得出一个数学公式,最后利用公式写代码。

这对看到数学公式就头疼星人不太友好,这边文章就是通过笔者3次提交,还原出如何不用数学公式推理出和官方题解一样的结果

第一次提交——简单但正确的思路

在题目描述中,有一个条件增大了题目难度:

使得剩下的 非空 袋子中(即 至少还有一颗 魔法豆的袋子)魔法豆的数目 相等

如果没有非空的要求,这个题目其实就是一个简单的计算题,只需要找到豆子最少的袋子,然后把其他袋子中多余的豆子拿出去,最后计算拿出去的总量即可。

增加了非空的要求,其实就有了变动的空间。像[1,10,11],如果没有非空要求,答案是19,加上非空要求,就可以通过把第一个袋子清空,再对后面两个袋子运用上面的过程,就可以减少到1 + 1 = 2

要解决非空条件带来的变化,思路也很简单:分别判断“不清空任何袋子”,“清空1个袋子”,“清空2个袋子”…,直到只剩下一个袋子,从所有结果中,找到最小的即可,当然要从豆子数量最小的开始清空。

所以,最终思路是:

  • 对所有袋子按照豆子数量,升序排序
  • 从豆子最少的袋子开始,
    • 不清空任何袋子,按照无非空条件的情况计算配平所有袋子需要拿出的豆子数目
    • 清空豆子最少的袋子,对剩余袋子按照无非空条件的情况计算配平所有袋子需要拿出的豆子数目
    • 清空豆子最少的两个袋子,对剩余袋子按照无非空条件的情况计算配平所有袋子需要拿出的豆子数目
    • ……
    • 清空除豆子最多的两个袋子外的所有袋子,对这两个袋子按照无非空条件的情况计算配平所有袋子需要拿出的豆子数目
  • 上述结果中的最小值就是我们需要的最终结果

根据上面的思路,容易写出如下C++代码:

class Solution {
public:
    long long minimumRemoval(vector<int>& beans) {
        // 1. 根据豆子数量,对袋子升序排序
        // 2. 从最小的袋子的开始清空
        // 3. 从清空0个开始,计算需要拿出的豆子
        int len = beans.size();
        sort(beans.begin(), beans.end()); // 升序排序
        
        long long res = -1;
        for(int i = 0;i < len;++i) {
            long long tmp = 0;
            // 计算清空前面小袋子需要拿出的豆子总数
            for(int j = 0;j < i;++j) {
                tmp += beans[j];
            }
            // 计算配平所有袋子需要拿出的袋子数量
            for(int j = i + 1;j < len;++j) {
                tmp += (beans[j] - beans[i]);
            }
            if(res < 0) {
                res = tmp;
            } else {
                res = min(res, tmp);
            }
        }
        return res; 
    }
};

提交后,发现会超时:

在这里插入图片描述

第二次提交——空间换时间,不再超时

现在我们可以思考一下,上面代码中有没有重复计算,是否可以优化其性能。

上述代码的循环中,主要有两个计算:

  1. 计算清空前面i个袋子需要拿出的豆子数量
  2. 计算配平后面len - i个袋子,需要拿出的豆子数量

第1个计算,可以用前缀和,在一个循环中计算,并记录下来。

对于第2个计算,所谓配平len - i个袋子,其实就是把[i + 1, i + 2, ..., len -1]袋子中多余的豆子拿出。

配平这些袋子需要拿出的豆子数就是图中红色部分:

在这里插入图片描述

可以直接计算出来:
假设:

  • 配平 i i i l e n − 1 len-1 len1袋子需要拿出的豆子数为 r i g h t i right_i righti
  • 所有豆子总数为 s u m sum sum
  • i i i个袋子中豆子总数为 l e f t _ s u m i left\_sum_i left_sumi

那么 i i i l e n − 1 len-1 len1袋子中豆子总数,也就是上图中所有方块的数目为 r i g h t _ s u m i = s u m − l e f t _ s u m i right\_sum_i = sum - left\_sum_i right_sumi=sumleft_sumi

配平 i i i l e n − 1 len-1 len1袋子需要拿出的豆子数,也就是图中红色部分 = 图中总方块数 - 蓝色部分,既 r i g h t i = r i g h t _ s u m i − ( l e n − i ) × b e a n s [ i ] right_i = right\_sum_i - (len - i) \times beans[i] righti=right_sumi(leni)×beans[i]

这样我们就可以提前计算1和2,那么在循环中就可以不再重复计算,对应的C++代码如下:

class Solution {
public:
    long long minimumRemoval(vector<int>& beans) {
        // 1. 根据豆子数量,对袋子升序排序
        // 2. 从最小的袋子的开始清空
        // 3. 从清空0个开始,计算需要拿出的豆子
        int len = beans.size();
        sort(beans.begin(), beans.end()); // 升序排序
        long long bean_sum = accumulate(beans.begin(), beans.end(), (long long)0); // 所有袋子中豆子总量

        vector<long long> left_sum(len + 1, 0); // 前i个袋子中豆子的总数
        for(int i = 1;i <= len;++i) {
            left_sum[i] = left_sum[i - 1] + beans[i - 1];
        }

        vector<long long> right(len, 0); // 配平i到len - 1袋子,需要拿出的豆子数量
        for(int i = 0;i < len;++i) {
            // 配平i到len - 1袋子需要拿出的豆子数 = 豆子总数 - 前面i个袋子中豆子总数 - 后面i到len - 1个袋子配平后的袋子总量
            right[i] = bean_sum - left_sum[i] - (len - i) * (long long)beans[i];
        }
        
        long long res = bean_sum;
        for(int i = 0;i < len;++i) {
            long long tmp = left_sum[i] + right[i];
            res = min(res, tmp);
        }
        return res; 
    }
};

这次提交已经可以成功通过了。

第三次提交——发现规律,简化代码

仔细观察上面代码,会发现计算right[i]时减去的left_sum[i]到循环阶段计算tmp的时候又加回去了。

也就是说第1个计算是多余的。

去掉第1个left_sum的计算,后面两个循环其实可以合并起来了,最终变成了和官方解法一样的结构,C++实现如下。

class Solution {
public:
    long long minimumRemoval(vector<int>& beans) {
        int len = beans.size();
        sort(beans.begin(), beans.end()); // 升序排序
        long long bean_sum = accumulate(beans.begin(), beans.end(), (long long)0); // 所有袋子中豆子总量

        long long res = bean_sum;
        for(int i = 0;i < len;++i) {
            res = min(res,bean_sum -  (len - i) * (long long)beans[i]);
        }
        
        return res; 
    }
};

结果还不错

在这里插入图片描述

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

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

相关文章

鼠害监测站的意义是什么

鼠害监测站是专门用于监测鼠害发生情况、种群结构和危害程度的设施。这些站点通常设立在农田、森林、草原等鼠害易发区域&#xff0c;通过定期调查和监测&#xff0c;收集鼠害相关信息&#xff0c;为防治工作提供科学依据。 TH-SH1 鼠害监测站的意义 保障农业生产&#xff1a;…

精品基于Uniapp+springboot美食菜谱类管理系统APP

《[含文档PPT源码等]精品基于Uniappspringboot美食类管理系统APP》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; 开发语言&#xff1a;Java 后台框架&#xff1a;springboot、ssm 安…

SpringBoot中整合MybatisPlus快速实现Mysql增删改查和条件构造器

场景 Mybatis-Plus(简称MP)是一个Mybatis的增强工具&#xff0c;只是在Mybatis的基础上做了增强却不做改变&#xff0c;MyBatis-Plus支持所有Mybatis原生的特性&#xff0c; 所以引入Mybatis-Plus不会对现有的Mybatis构架产生任何影响。MyBatis 增强工具包&#xff0c;简化 C…

掌握退款与测评自养号技术,在亚马逊、沃尔玛上轻松做卖家

今天&#xff0c;我想与大家分享在亚马逊、沃尔玛退款自养号中的一些经验。众所周知&#xff0c;自养号的环境是至关重要的&#xff0c;它涉及到系统的纯净度、下单所用的信用卡以及许多其他细节。一个良好的养号环境能够确保账号的安全与稳定&#xff0c;进而提高退款成功率。…

2023年暴涨130%后,嘉年华游轮股价2024年还会继续暴涨吗?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 2023年对嘉年华游轮来说的标志性的一年 2023年&#xff0c;嘉年华游轮(CCL)的业务不但实现了全面复苏&#xff0c;而且其股价也重新回到了市场领先地位&#xff0c;全年上涨了130%&#xff0c;远远超过了标普500指数24%的涨…

数据库结构、数据对比及同步

目录 一、场景二、操作Navicat Premium 一、场景 部署服务时需要确保开发环境的数据库与生产环境的数据库结构、数据一致 这时可以通过Navicat Premium、SQLyog等工具进行数据库对比 二、操作 Navicat Premium 1、选择同步类型 2、选择对比的数据库 3、选择对比参数 4、查看…

预处理详解(#和##运算符、命名约定、#undef​​、命令行定义​、条件编译、头文件的包含​)

目录 一、#和## 1.1#运算符 1.2## 运算符​ 二、命名约定​ 三、#undef​ 四、命令行定义​ 五、条件编译​ 六、头文件的包含​ 4.1 头文件被包含的方式&#xff1a;​ 4.1.1 本地文件包含​ Linux环境的标准头文件的路径&#xff1a;​ VS环境的标准头文件的路径&…

HelloWorld(java)

1.切换盘符&#xff1a;找到刚刚书写的代码 2.编译&#xff1a;javac是JDK提供的编译工具&#xff0c;通过这个工具&#xff0c;把当前路径下下的HelloWorld.java文件编译成class文件 3.运行&#xff1a;java也是JDK提供的一个工具&#xff0c;作用就是用来运行代码&#xff…

【漏洞复现】银达汇智智慧综合管理平台任意文件读取漏洞

Nx01 产品简介 福建银达汇智信息科技股份有限公司成立于2009年&#xff0c;位于福建省福州市&#xff0c;是一家以从事软件和信息技术服务业为主的企业。 Nx02 漏洞描述 银达汇智智慧综合管理平台 FileDownLoad.aspx 存在任意文件读取漏洞&#xff0c;通过漏洞攻击者可下载服务…

项目管理十大知识领域之成本管理

1. 项目成本管理的意义和重要性 项目成本管理是项目管理中至关重要的一部分&#xff0c;它直接关系到项目最终成本和利润的控制&#xff0c;对于企业的可持续发展具有重要意义。通过合理的成本管理&#xff0c;项目能够更好地控制预算&#xff0c;提高效率&#xff0c;降低成本…

计算机系统基础知识一、数值的源码、反码、补码、移码

目录 一、原码、反码、补码定义 1、原码表示 2、反码表示 3、补码表示 二、算数运算 1、二进制算数运算规则 2、机器数的加减运算 三、移码定义 四、移码的意义 概要 在计算机基础中&#xff0c;原码、反码、补码和移码是用于表示和处理有符号整数的编码方式。它们…

5大自动化测试的Python框架,快来学习!

自从2018年被评选为编程语言以来&#xff0c;Python在各大排行榜上一直都是名列前茅。 目前&#xff0c;它在Tiobe指数中排名第三个&#xff0c;仅次于Java和C。随着该编程语言的广泛使用&#xff0c;基于Python的自动化测试框架也应运而生&#xff0c;且不断发展与丰富。 因…

MATLAB解决考研数学一题型(上)

闲来无事&#xff0c;情感问题和考研结束后的戒断反应比较严重&#xff0c;最近没有什么写博文的动力&#xff0c;抽空来整理一下考研初试前一直想做的工作——整理一下MATLAB解决数学一各题型的命令~ 本贴的目录遵循同济版的高数目录~ 目录 一.函数与极限 1.计算双侧极限 2…

鸿蒙开发-UI-布局-弹性布局

地方 鸿蒙开发-UI-布局 鸿蒙开发-UI-布局-线性布局 鸿蒙开发-UI-布局-层叠布局 文章目录 前言 一、基本概念 二、布局方向 1、主轴为水平方向 2、主轴为垂直方向 三、布局换行 四、对齐方式 1、主轴对齐方式 2、交叉轴对齐方式 2.1、容器组件设置交叉轴对齐 2.2、子组件设置交叉…

安装脚手架Vue CLI详解!!!

Vue CLI基本介绍&#xff1a; Vue CLI是Vue官方提供的一个全局命令工具。可以帮助我们快速创建一个开发Vue项目的标准化基础架子【集成了webpack配置】 安装脚手架好处&#xff1a; 开箱即用&#xff0c;零配置&#xff1b;内置babel等工具&#xff1b;标准化 安装步骤&#…

javaScript设计模式-工厂

它的好处是消除对象间的耦合度&#xff0c;在派生子类时提供了更大的灵活性。但盲目的把普通的构造函数扔在一边&#xff0c;并不值得提倡。如果要采一不可能另外换用一个类&#xff0c;或都不需要在运行期间在一系列可互换的类中进行选择&#xff0c;就不应该使用。这样在后期…

虚拟环境中pip install不生效的解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

全网首发 2024华数杯B题成品论文word52页(附带所有可执行代码+和高质量数据)

基于数据分析下的光伏发电 摘 要&#xff08;文末获取&#xff09; 根据最新数据&#xff0c;中国的总发电量超过20万亿千瓦时&#xff0c;总体排名世界第一&#xff0c;而光伏发电是一种重要的可再生能源&#xff0c;可以将太阳能转化为电能可以减少对传统能源的依赖&#x…

本地运行LlaMA 2的简易指南

大家好&#xff0c;像LLaMA 2这样的新开源模型已经变得相当先进&#xff0c;并且可以免费使用。可以在商业上使用它们&#xff0c;也可以根据自己的数据进行微调&#xff0c;以开发专业版本。凭借其易用性&#xff0c;现在可以在自己的设备上本地运行它们。 本文将介绍如何下载…

Python 语言零基础入门,需要做哪些准备?

Python 语言零基础入门&#xff0c;需要做哪些准备&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Python的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&…