C++前缀和算法的应用:得到连续 K 个 1 的最少相邻交换次数 原理源码测试用例

简介: C++前缀和算法的应用:得到连续 K 个 1 的最少相邻交换次数 原理源码测试用例

本文涉及的基础知识点

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

滑动窗口

题目

给你一个整数数组 nums 和一个整数 k 。 nums 仅包含 0 和 1 。每一次移动,你可以选择 相邻 两个数字并将它们交换。

请你返回使 nums 中包含 k 个 连续 1 的 最少 交换次数。

示例 1:

输入:nums = [1,0,0,1,0,1], k = 2

输出:1

解释:在第一次操作时,nums 可以变成 [1,0,0,0,1,1] 得到连续两个 1 。

示例 2:

输入:nums = [1,0,0,0,0,0,1,1], k = 3

输出:5

解释:通过 5 次操作,最左边的 1 可以移到右边直到 nums 变为 [0,0,0,0,0,1,1,1] 。

示例 3:

输入:nums = [1,1,0,1], k = 2

输出:0

解释:nums 已经有连续 2 个 1 了。

提示:

1 <= nums.length <= 105

nums[i] 要么是 0 ,要么是 1 。

1 <= k <= sum(nums)

分析

假定

nums[left]和nums[r]都是1,且nums[left,r]共有k个1。

左移(右移)的顺序不影响结果

换种思考方式,将0移出。假定left < m0 < m1 < r。先移m0,移动m1,需要m0-left,移动m1,需要m1-(left+1),共需要m0+m1-left2-1。先移m1,移动m0,需要m1-left,移动m0,需要m0-(left+1),共需要m0+m1-left2-1。

公式:如果有n个数左移,则交换次数为:这些数距离left的和-n*(n-1)/2。

如果m1左移更划算,那么m0左移也更划算

m0相比与m1,左移消耗更少,右移消耗更多。显然左移更划算。右移类似。

代码解释

vOneIndex 依次记录了nums[i]等于1的索引。m是[left,r]中第一个右移划算的0。
[left,m)中的0 左移,[m,end)中的0右移。由于nums[end]是1,所以[m,end]中的0,就是[m,end)中的0。
v0Dis[m] - v0Dis[left] [left,m)中的0 全部移到索引0处需要的交换次数。
left * iLeft0Num [left,m)中的0 全部从left移动0的交换次数。
两种相减就是 [left,m)中的0 全部 移动到left,处需要的交换次数。
r * iRight0Num 是[m,r)中的0全部从r移动到0。
v0Dis[r] - v0Dis[m ] [m,r)中的0全部移动到0。
两者相减 就是[m,r)的0全部移到r需要的次数。

时间复杂度

O(n)。枚举i,时间复杂度O(n);枚举m,时间复杂度O(n)。注意m不是从头开始,所以枚举m的总时间复杂度是O(n),而不是每个i都是O(n)。

代码

核心代码

class Solution {
public:
int minMoves(vector& nums, int k) {
m_c = nums.size();
vector vOneIndex;
for (int i = 0; i < m_c ; i++)
{
if (1 == nums[i])
{
vOneIndex.emplace_back(i);
}
}
vector v0Dis = { 0 };//记录nums[0,i)中,nums[i]等于0时 i之和,也就是将所有nums[i]移到0处
for (int i = 0; i < m_c; i++)
{
long long llAdd = (0 == nums[i]) ? i : 0;
v0Dis.emplace_back(llAdd+v0Dis.back());
}
vector v0Num = { 0 };//记录nums[0,i)中0的个数
for (const auto& n : nums)
{
v0Num.emplace_back(v0Num.back() + (0==n));
}
long long llRet = INT_MAX;
int m = 0;
for (int i = 0; i + k - 1 < vOneIndex.size(); i++)
{
const int left = vOneIndex[i];
const int r = vOneIndex[i + k - 1];
if (m < left)
{
m = left + 1;
}
for (; m < r; m++)
{
if (1 == nums[m])
{
continue;
}
//[left,m)中的0 左移
const int iLeft0Num = v0Num[m] - v0Num[left];
//[m,end)中的0右移,由于nums[end]是1,所以[m,end]中的0,就是[m,end)中的0
const int iRight0Num = v0Num[r] - v0Num[m];
const int iLeftCur = m - left - iLeft0Num;
const int iRightCur = r - m - (iRight0Num - 1);
if (iRightCur <= iLeftCur)
{
break;
}
}
//m 等于r,也符合下面的逻辑[left,r)和[r,r),右移为空
const long long iLeft0Num = v0Num[m] - v0Num[left];
const long long iRight0Num = v0Num[r] - v0Num[m];
const long long llLeftMove = v0Dis[m] - v0Dis[left] - left * iLeft0Num - (iLeft0Num - 1) * iLeft0Num / 2;
const long long llRightMove = r * iRight0Num - (v0Dis[r] - v0Dis[m]) - (iRight0Num - 1) * iRight0Num / 2;
llRet = min(llRet, llLeftMove + llRightMove);
}
return llRet;
}
int m_c;
};

测试用例

template
void Assert(const vector& v1, const vector& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i] == v2[i]);
}
}
template
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
int main()
{
vector nums = { 1, 0, 0, 1, 0, 1 };
int k = 2;
auto res = Solution().minMoves(nums,k);
Assert(1, res);
nums = { 1, 0, 0, 0, 0, 0, 1, 1 };
k = 3;
res = Solution().minMoves(nums, k);
Assert(5, res);
nums = { 1,1,0,1 };
k = 2;
res = Solution().minMoves(nums, k);
Assert(0, res);
//CConsole::Out(res);

}

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步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

相关文章
|
4月前
|
运维 监控 JavaScript
基于 Node.js 图结构的局域网设备拓扑分析算法在局域网内监控软件中的应用研究
本文探讨图结构在局域网监控系统中的应用,通过Node.js实现设备拓扑建模、路径分析与故障定位,提升网络可视化、可追溯性与运维效率,结合模拟实验验证其高效性与准确性。
308 3
|
4月前
|
机器学习/深度学习 资源调度 算法
遗传算法模型深度解析与实战应用
摘要 遗传算法(GA)作为一种受生物进化启发的优化算法,在复杂问题求解中展现出独特优势。本文系统介绍了GA的核心理论、实现细节和应用经验。算法通过模拟自然选择机制,利用选择、交叉、变异三大操作在解空间中进行全局搜索。与梯度下降等传统方法相比,GA不依赖目标函数的连续性或可微性,特别适合处理离散优化、多目标优化等复杂问题。文中详细阐述了染色体编码、适应度函数设计、遗传操作实现等关键技术,并提供了Python代码实现示例。实践表明,GA的成功应用关键在于平衡探索与开发,通过精心调参维持种群多样性同时确保收敛效率
|
4月前
|
机器学习/深度学习 边缘计算 人工智能
粒子群算法模型深度解析与实战应用
蒋星熠Jaxonic是一位深耕智能优化算法领域多年的技术探索者,专注于粒子群优化(PSO)算法的研究与应用。他深入剖析了PSO的数学模型、核心公式及实现方法,并通过大量实践验证了其在神经网络优化、工程设计等复杂问题上的卓越性能。本文全面展示了PSO的理论基础、改进策略与前沿发展方向,为读者提供了一份详尽的技术指南。
粒子群算法模型深度解析与实战应用
|
4月前
|
机器学习/深度学习 算法 安全
小场景大市场:猫狗识别算法在宠物智能设备中的应用
将猫狗识别算法应用于宠物智能设备,是AIoT领域的重要垂直场景。本文从核心技术、应用场景、挑战与趋势四个方面,全面解析这一融合算法、硬件与用户体验的系统工程。
|
安全 Java 测试技术
python接口自动化(三)--如何设计接口测试用例(详解)
上篇我们已经介绍了什么是接口测试和接口测试的意义。在开始接口测试之前,我们来想一下,如何进行接口测试的准备工作。或者说,接口测试的流程是什么?有些人就很好奇,接口测试要流程干嘛?不就是拿着接口文档直接利用接口 测试工具测试嘛。其实,如果只是三五个接口,你可以这么做一个临时的接口测试。但是,如果是上百个接口,或者,你们公司的这个项目,第一次做接口测试,那么,我们还是很有必要严格遵守接口测试的流程。
564 0
python接口自动化(三)--如何设计接口测试用例(详解)
|
测试技术
正交试验测试用例设计及工具推荐
在科研和生产实践中,人们往往要做许多次实验来进行某项研究。实验条件一般包括很多因素,当因素的值不同时,实验的结果也不一样。如果想把每个因素的每个值都要实验一遍,总实验数就等于各因素的值的个数的乘积,而这个数往往很大,超过了可接受的成本。 例如,假设某个实验由A,B,C,D四个因素,每个因素都有10个不同的取值,那么如果想把每个因素都考虑到,我们需要做 10*10*10*10=10000次实验。 为了减少实验数目,我们必须选出那些最有代表性的例子。于是,就要用到了正交表法(Orthogonal Array Testing Strategy)。
888 0
正交试验测试用例设计及工具推荐
|
算法 安全 测试技术
【软件测试】测试用例的设计方法
测试用例写的过于简单,则可能失去了测试用例的意义,设计过于简单的测试用例其实并没有真正的进行设计,只是把需要测试的功能模块记录下来而已,它的作用仅仅是在测试过程中作为一个简单的测试计划,提醒测试人员测试的主要功能包括哪些而已,测试用例设计的本质应该是在设计的过程中理解需求,检验需求,并把对软件系统的测试方法的思路记录下来,以便指导将来的测试
【软件测试】测试用例的设计方法
|
安全 中间件 测试技术
【面试高频】给你一句话需求,让你设计测试用例,该怎么做?
【面试高频】给你一句话需求,让你设计测试用例,该怎么做?
【面试高频】给你一句话需求,让你设计测试用例,该怎么做?
|
消息中间件 缓存 中间件
【测试基础】二、我好像真的不会设计“好的”测试用例
【测试基础】二、我好像真的不会设计“好的”测试用例
【测试基础】二、我好像真的不会设计“好的”测试用例
|
JSON 前端开发 测试技术
手把手带你设计接口自动化测试用例(一):提取接口信息并分析
手把手带你设计接口自动化测试用例(一):提取接口信息并分析
924 0
手把手带你设计接口自动化测试用例(一):提取接口信息并分析