【对顶队列】【中位数贪心】【前缀和】100227. 拾起 K 个 1 需要的最少行动次数

本文涉及知识点

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
对顶队列(栈) 分类讨论

LeetCode100227. 拾起 K 个 1 需要的最少行动次数

给你一个下标从 0 开始的二进制数组 nums,其长度为 n ;另给你一个 正整数 k 以及一个 非负整数 maxChanges 。
灵茶山艾府在玩一个游戏,游戏的目标是让灵茶山艾府使用 最少 数量的 行动 次数从 nums 中拾起 k 个 1 。游戏开始时,灵茶山艾府可以选择数组 [0, n - 1] 范围内的任何索引index 站立。如果 nums[index] == 1 ,灵茶山艾府就会拾起一个 1 ,并且 nums[index] 变成0(这 不算 作一次行动)。之后,灵茶山艾府可以执行 任意数量 的 行动(包括零次),在每次行动中灵茶山艾府必须 恰好 执行以下动作之一:
选择任意一个下标 j != index 且满足 nums[j] == 0 ,然后将 nums[j] 设置为 1 。这个动作最多可以执行 maxChanges 次。
选择任意两个相邻的下标 x 和 y(|x - y| == 1)且满足 nums[x] == 1, nums[y] == 0 ,然后交换它们的值(将 nums[y] = 1 和 nums[x] = 0)。如果 y == index,在这次行动后灵茶山艾府拾起一个 1 ,并且 nums[y] 变成 0 。
返回灵茶山艾府拾起 恰好 k 个 1 所需的 最少 行动次数。
示例 1:
输入:nums = [1,1,0,0,0,1,1,0,0,1], k = 3, maxChanges = 1
输出:3
解释:如果游戏开始时灵茶山艾府在 index == 1 的位置上,按照以下步骤执行每个动作,他可以利用 3 次行动拾取 3 个 1 :
游戏开始时灵茶山艾府拾取了一个 1 ,nums[1] 变成了 0。此时 nums 变为 [1,0,0,0,0,1,1,0,0,1] 。
选择 j == 2 并执行第一种类型的动作。nums 变为 [1,0,1,0,0,1,1,0,0,1]
选择 x == 2 和 y == 1 ,并执行第二种类型的动作。nums 变为 [1,1,0,0,0,1,1,0,0,1] 。由于 y == index,灵茶山艾府拾取了一个 1 ,nums 变为 [1,0,0,0,0,1,1,0,0,1] 。
选择 x == 0 和 y == 1 ,并执行第二种类型的动作。nums 变为 [0,1,0,0,0,1,1,0,0,1] 。由于 y == index,灵茶山艾府拾取了一个 1 ,nums 变为 [0,0,0,0,0,1,1,0,0,1] 。
请注意,灵茶山艾府也可能执行其他的 3 次行动序列达成拾取 3 个 1 。

示例 2:

输入:nums = [0,0,0,0], k = 2, maxChanges = 3

输出:4

解释:如果游戏开始时灵茶山艾府在 index == 0 的位置上,按照以下步骤执行每个动作,他可以利用 4 次行动拾取 2 个 1 :

选择 j == 1 并执行第一种类型的动作。nums 变为 [0,1,0,0] 。
选择 x == 1 和 y == 0 ,并执行第二种类型的动作。nums 变为 [1,0,0,0] 。由于 y == index,灵茶山艾府拾起了一个 1 ,nums 变为 [0,0,0,0] 。
再次选择 j == 1 并执行第一种类型的动作。nums 变为 [0,1,0,0] 。
再次选择 x == 1 和 y == 0 ,并执行第二种类型的动作。nums 变为 [1,0,0,0] 。由于y == index,灵茶山艾府拾起了一个 1 ,nums 变为 [0,0,0,0] 。
提示:
2 <= n <= 105
0 <= nums[i] <= 1
1 <= k <= 105
0 <= maxChanges <= 105
maxChanges + sum(nums) >= k

分类讨论

1,消耗0行动次数。nums[index]为1。
2,消耗1行动次数,nums[index-1]或nums[index+1]为1。
3,消耗2行动次数,一转换次数。nums[index-1]或nums[index+1]设置为1,再移动。
4,消耗2或更多行动次数。将其它的1移动过来。 ⟺ \iff 计算距离index 最近的m个一。

枚举index,时间复杂度O(n)。前三种分类,时间复杂度都是O(1),如何将第四类操作的时间复杂度将为O(1)。
如果有转换次数,一定用分类三,不用分类四。故需要讨论分类四时,maxChanges 次数已经用完,故m = k - maxChange。 用对顶队列分别记录下标小于index 的1下标,下标大于等于index的1下标。

程序流程

一,用队列升序收集1的下标。
二,利用对顶队列计算 距离index最近的m个1。
三,分别处理情况一到四。注意:拾取次数不能超过k。m个1要扣除分类一和分类二的1。

对顶队列预处理最近m个1

一,计算vPre[0]。
二,如果队列不发生变化,vPre[i2] = vPre[i2 - 1] + que1.size() - que2.size();
三,如果que2移到que1,触发条件que2.front() < i2。由于i2一次加1,所以que2.front()此时一定等于i2-1。应该是1,误算成-1。故+2。 que1入队,que2出队。
三,枚举i2是,同时 枚举que1Index。index1.front()一定> i2,否则ndex1.front() < i2-1,应该进入que1。index是升序,故que1中的下标都小于ndex1.front(),故ndex1.front()比que1中的数距离i2-1都近。一定会淘汰que1中的数据。
因为ndex1.front()大于que2中的下标,故不会淘汰que2中的数。
que1中的数,越小越容易淘汰,故从队首开始淘汰。

代码

核心代码

class Solution {
public:long long minimumMoves(const vector<int>& nums, const int k, const int maxChanges) {m_c = nums.size();queue<int> que1Index;for (int i = 0; i < m_c; i++){if (nums[i]){que1Index.emplace(i);}}vector<long long> vPre(nums.size());//距离 nums[i]最近的iNum个1的距离const int i1Num = k - maxChanges;		queue<int> que1, que2;while(que1Index.size() && (que2.size() < i1Num)){//计算vPre[0]que2.emplace(que1Index.front());vPre[0] += que1Index.front();que1Index.pop();}for (int i2 = 1; i2 < m_c; i2++){vPre[i2] = vPre[i2 - 1] + que1.size() - que2.size();if (que2.size() && (que2.front() < i2)){//队列二向队列一移动que1.emplace(que2.front());que2.pop();vPre[i2] += 1 + 1;}while (que1.size() && que1Index.size() && (i2 - que1.front() > que1Index.front() - i2)){vPre[i2] -= (i2 - que1.front());vPre[i2] += que1Index.front() - i2;que2.emplace(que1Index.front());que1.pop();que1Index.pop();}}long long llRet = LLONG_MAX;;for (int i = 0; i < nums.size(); i++){int iNeed = k - nums[i];	long long llDo = 0;if ((iNeed > 0) && (i > 0) && nums[i - 1]){iNeed--; llDo++;}if ((iNeed > 0) && (i+1 < nums.size()) && nums[i + 1]){iNeed--; llDo++;}const int iNeiBo = llDo;const int iChange = min(maxChanges, iNeed);iNeed -= iChange;llDo += 2 * iChange;if (iNeed > 0){llDo += vPre[i] - iNeiBo;}llRet = min(llRet, llDo);}return llRet;}int m_c;
};

测试用例

template<class T, class T2>
void Assert(const T& t1, const T2& t2)
{assert(t1 == t2);
}template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{if (v1.size() != v2.size()){assert(false);return;}for (int i = 0; i < v1.size(); i++){Assert(v1[i], v2[i]);}}int main()
{vector<int> nums;int k, maxChanges;{nums = { 1,0,1,0,1 }, k = 3, maxChanges = 0;auto res = Solution().minimumMoves(nums, k, maxChanges);Assert(4, res);}{nums = { 1, 1, 0, 0, 0, 1, 1, 0, 0, 1 }, k = 3, maxChanges = 1;auto res = Solution().minimumMoves(nums, k, maxChanges);Assert(3, res);}{nums = { 0, 0, 0, 0 }, k = 2, maxChanges = 3;auto res = Solution().minimumMoves(nums, k, maxChanges);Assert(4, res);}
}

优化

如果有情况四,情况一二三,可以统一。

class Solution {
public:long long minimumMoves(const vector<int>& nums, const int k, const int maxChanges) {m_c = nums.size();queue<int> que1Index;for (int i = 0; i < m_c; i++){if (nums[i]){que1Index.emplace(i);}}vector<long long> vPre(nums.size());//距离 nums[i]最近的iNum个1的距离const int i1Num = k - maxChanges;		queue<int> que1, que2;while(que1Index.size() && (que2.size() < i1Num)){//计算vPre[0]que2.emplace(que1Index.front());vPre[0] += que1Index.front();que1Index.pop();}for (int i2 = 1; i2 < m_c; i2++){vPre[i2] = vPre[i2 - 1] + que1.size() - que2.size();if (que2.size() && (que2.front() < i2)){//队列二向队列一移动que1.emplace(que2.front());que2.pop();vPre[i2] += 1 + 1;}while (que1.size() && que1Index.size() && (i2 - que1.front() > que1Index.front() - i2)){vPre[i2] -= (i2 - que1.front());vPre[i2] += que1Index.front() - i2;que2.emplace(que1Index.front());que1.pop();que1Index.pop();}}long long llRet = LLONG_MAX;;for (int i = 0; i < nums.size(); i++){int iNeed = k - nums[i];	long long llDo = 0;if ((iNeed > 0) && (i > 0) && nums[i - 1]){iNeed--; llDo++;}if ((iNeed > 0) && (i+1 < nums.size()) && nums[i + 1]){iNeed--; llDo++;}if (maxChanges >= iNeed){llDo += 2 * iNeed;}else{llDo = vPre[i] + maxChanges * 2;}llRet = min(llRet, llDo);}return llRet;}int m_c;
};

中位数贪心

如果某个数距离m个数距离最短,那么它一定是正中间的数。也就是情况四:左边m/2个1,右边m-1-m/2个1。用前缀和计算。
nums[index]不会为0。
一,000。根据中位数贪心,相比换到这m个数的中心。情况一二四一定不会有优势。情况一二也没优先。
二,100,同上。
三,101。假定101左边有m1个1,右边有m2个1。如果m1 <= m2,移到101 ,否则移到101
除101外的数距离全部变短。101的距离从:1,1变成 0,2 持平。

如何计算左边m1个和

vPre[i]记录前i个1到0的距离。
故第j个1前面的m1个。m1 × \times × index[j]- (vPre[j]-vPre[j-m1])
故第j个1后面的m2个 vPre[j+1+m2] - vPre[j+1] - m2 × \times ×index[j]

特例

全部是0。

代码

class Solution {
public:long long minimumMoves(const vector<int>& nums, const int k, const int maxChanges) {m_c = nums.size();vector<int> v1Index;for (int i = 0; i < m_c; i++){if (nums[i]){v1Index.emplace_back(i);}}if (v1Index.empty()){return k * 2;}vector<long long> vPre = { 0 };for (const auto& n : v1Index){vPre.emplace_back(vPre.back() + n);}long long llRet = LLONG_MAX;;for (int j = 0 ; j < v1Index.size();j++ ){const int& i = v1Index[j];int iNeed = k - nums[i];	long long llDo = 0;if ((iNeed > 0) && (i > 0) && nums[i - 1]){iNeed--; llDo++;}if ((iNeed > 0) && (i+1 < nums.size()) && nums[i + 1]){iNeed--; llDo++;}if (maxChanges >= iNeed){llDo += 2 * iNeed;}else{const int m = k - maxChanges;const long long m1 = m / 2;const long long m2 = m - m1 - 1;if ((j - m1 < 0) || (j + 1 + m2 >= vPre.size())){continue;}const long long llLeft = m1 * i - (vPre[j] - vPre[j - m1]);const long long llRight = vPre[j + 1 + m2] - vPre[j + 1] - m2 * i;llDo = llLeft + llRight + maxChanges * 2;}llRet = min(llRet, llDo);}return llRet;}int m_c;
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

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

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

相关文章

python redis中blpop和lpop的区别

python redis中lpop()方法是获取并删除左边第一个对象。 def lpop(self,name: str,count: Optional[int] None,) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:"""Removes and returns the first elements of the list name.By de…

VR历史建筑漫游介绍|虚拟现实体验店加盟|VR设备购买

VR历史建筑漫游是一种利用虚拟现实技术&#xff0c;让用户可以身临其境地参观和探索历史建筑的体验。通过VR头显和相关设备&#xff0c;用户可以在虚拟环境中自由移动和互动&#xff0c;感受历史建筑的真实氛围和文化内涵。 在VR历史建筑漫游中&#xff0c;您可以选择不同的历史…

为什么手机和电视ip地址不一样

在数字化时代&#xff0c;我们每天都会与各种电子设备打交道&#xff0c;其中最常见的就是手机和电视。当我们连接到互联网时&#xff0c;这些设备都会被分配一个独特的IP地址&#xff0c;用于在网络上进行标识和通信。然而&#xff0c;您可能已经注意到&#xff0c;即使手机和…

基于java+springboot+vue实现的高校教师工作量管理系统(文末源码+Lw+ppt)23-451

摘 要 高校教师工作量管理系统采用B/S架构&#xff0c;数据库是MySQL。网站的搭建与开发采用了先进的java进行编写&#xff0c;使用了springboot框架。该系统从两个对象&#xff1a;由管理员和教师来对系统进行设计构建。主要功能包括&#xff1a;个人信息修改&#xff0c;对…

vue2点击左侧的树节点(el-tree)定位到对应右侧树形表格(el-table)的位置,树形表格懒加载

左侧树代码 <el-tree :data"treeData" node-key"id" default-expand-all"" //节点默认全部展开:expand-on-click-node"false" //是否在点击节点的时候展开或者收缩节点:props"defaultProps" node-click"handleNodeC…

《LeetCode热题100》笔记题解思路技巧优化_Part_2

《LeetCode热题100》笔记&题解&思路&技巧&优化_Part_2 &#x1f60d;&#x1f60d;&#x1f60d; 相知&#x1f64c;&#x1f64c;&#x1f64c; 相识&#x1f622;&#x1f622;&#x1f622; 开始刷题普通数组&#x1f7e1;1. 最大子数组和&#x1f7e1;2. 合…

【FX110】突发:经纪商CAPITAL.COM暂停接受英国客户

经纪商 Capital.com 宣布暂时停止接收来自英国的新客户。 当访问英国的Capital.com网站并尝试开设账户时&#xff0c;用户会收到一条消息&#xff0c;指出该公司“已决定暂时暂停在英国接纳新客户”。 声明表示&#xff0c;“作为一家企业&#xff0c;我们发展迅速&#xff0c;…

无人机三维建模过程中注意事项

无人机三维建模是指利用无人机技术进行三维建模&#xff0c;该方法通过无人机搭载的多种传感器&#xff0c;如摄像头、激光扫描仪等&#xff0c;获取建筑物的多角度影像数据&#xff0c;然后利用计算机视觉技术和三维重建算法&#xff0c;将这些影像数据转化为高精度的三维模型…

【送书福利第五期】:ARM汇编与逆向工程

文章目录 &#x1f4d1;前言一、ARM汇编与逆向工程1.1 书封面1.2 内容概括1.3 目录 二、作者简介三、译者介绍&#x1f324;️、粉丝福利 &#x1f4d1;前言 与传统的CISC&#xff08;Complex Instruction Set Computer&#xff0c;复杂指令集计算机&#xff09;架构相比&#…

ARM Cortex R52内核 01 概述

ARM Cortex R52内核 01 Introduction 1.1 Cortex-R52介绍 Cortex-R52处理器是一种中等性能、有序、超标量处理器&#xff0c;主要用于汽车和工业应用。它还适用于各种其他嵌入式应用&#xff0c;如通信和存储设备。 Cortex-R52处理器具有一到四个核心&#xff0c;每个核心实…

C#装箱和拆箱

一&#xff0c;装箱 装箱是指将值类型转化为引用类型。 代码如下&#xff1a; 装箱的内部过程 当值类型需要被装箱为引用类型时&#xff0c;CLR&#xff08;Common Language Runtime&#xff09;会为值类型分配内存&#xff0c;在堆上创建一个新的对象。值类型的数据会被复…

Github: Github actions自动化工作原理与多workflow创建和部署

Github actions 1 &#xff09;概述 Github Actions 是Github官方推出的 CI/CD 解决方案 https://docs.githu.com/en/actions 优点 自动发布流程可减少发布过程中手动操作成本&#xff0c;大幅提升ci/cd效率&#xff0c;快速实现项目发布上线 缺点 存在较高的技术门槛需要利用…

【Git教程】(八)版本库间的交换 —— 版本库的克隆与命名,分支监控、命名、拉取及推送 ~

Git教程 版本库间的交换 1️⃣ 克隆版本库2️⃣ 如何告知 Git 其他版本库的位置3️⃣ 给版本库添加别名4️⃣ 获取数据5️⃣ 远程跟踪分支&#xff1a;监控其他分支6️⃣ 利用本地分支操作别处的版本库7️⃣ PULL操作8️⃣ PUSH操作9️⃣ 命名分支&#x1f33e; 总结 Git 是个…

elementUI两个select单选框联动

实现需求&#xff1a;两个单选框内容两栋&#xff0c;在选择第一个时&#xff0c;第二个选框能自动更新对应选项。且在切换第一个选项内容时&#xff0c;第二个选框会被清空且切换到新的对应选项。 设置值班班次和备班情况两个选项 &#xff0c;完整代码如下&#xff1a; <…

论文阅读_时序模型_iTransformer

1 2 3 4 5 6 7 8英文名称: ITRANSFORMER: INVERTED TRANSFORMERS ARE EFFECTIVE FOR TIME SERIES FORECASTING 中文名称: ITRANSFORMER&#xff1a;倒置Transformers在时间序列预测中的有效性 链接: https://openreview.net/forum?idX6ZmOsTYVs 代码: https://github.com/thum…

【排序算法】-- 深入理解桶排序算法

概述 在计算机科学中&#xff0c;排序算法是一种对数据进行有序排列的重要技术。桶排序&#xff08;Bucket Sort&#xff09;是一种常见的排序算法&#xff0c;它通过将数据分到有限数量的桶中&#xff0c;并对每个桶中的数据分别排序&#xff0c;最后按照顺序将所有桶中的数据…

机器视觉学习(四)—— 图像的色彩

目录 一、图像的基础知识 二、NumPy模块 三、图像色彩变化 3.1 RGB图像的分通道显示 3.2 HSV图像的分通道显示 一、图像的基础知识 总结的笔记&#xff1a; """ 二值图: 每个像素取值 0或1,图像显示出来只有黑白色; 黑色:0 白色:1 灰度图: …

IoT 物联网场景中 LoRa + 蓝牙Bluetooth 室内场馆高精定位技术全面解析

基于LoRa蓝牙的室内场景定位技术&#xff0c;蓝牙主要负责位置服务&#xff0c;LoRa主要负责数据传输。 01 LoRa和蓝牙技术 LoRa全称 “Long Rang”&#xff0c;是一种成熟的基于扩频技术的低功耗、超长距离的LPWAN无线通信技术。LoRa主要采用的是窄带扩频技术&#xff0c;抗干…

基于深度学习的口罩人脸识别研究进展

MTCNN模型训练输入的所有图像都是正样本&#xff08;戴口罩的照片&#xff09;&#xff0c;没有负样本作为模型输入。在后续的识别任务模块中&#xff0c;导入MTCNN模型检测结果&#xff0c;对特征点进行编码比较进行识别。 基于MTCNN的口罩人脸识别框架可分为四个阶段&#xf…

大美博罗迎盛会,“村ART“点亮新征程

三月的博罗,春意盎然,生机勃勃。在这万物复苏的美好时节,首届"村ART"乡村艺术作品评比大赛盛大开启。本次大赛由博罗县政府和泰康保险集团联合主办,以"农民画农民、农民画农村"为主题,旨在为广大农民朋友搭建一个展示才华、抒发情怀的广阔舞台,用艺术之光点…