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

相关文章
|
1天前
|
机器学习/深度学习 人工智能 自然语言处理
解锁机器学习的新维度:元学习的算法与应用探秘
元学习作为一个重要的研究领域,正逐渐在多个应用领域展现其潜力。通过理解和应用元学习的基本算法,研究者可以更好地解决在样本不足或任务快速变化的情况下的学习问题。随着研究的深入,元学习有望在人工智能的未来发展中发挥更大的作用。
|
1天前
|
算法 Serverless 数据处理
从集思录可转债数据探秘:Python与C++实现的移动平均算法应用
本文探讨了如何利用移动平均算法分析集思录提供的可转债数据,帮助投资者把握价格趋势。通过Python和C++两种编程语言实现简单移动平均(SMA),展示了数据处理的具体方法。Python代码借助`pandas`库轻松计算5日SMA,而C++代码则通过高效的数据处理展示了SMA的计算过程。集思录平台提供了详尽且及时的可转债数据,助力投资者结合算法与社区讨论,做出更明智的投资决策。掌握这些工具和技术,有助于在复杂多变的金融市场中挖掘更多价值。
22 12
|
2月前
|
存储 缓存 算法
探索企业文件管理软件:Python中的哈希表算法应用
企业文件管理软件依赖哈希表实现高效的数据管理和安全保障。哈希表通过键值映射,提供平均O(1)时间复杂度的快速访问,适用于海量文件处理。在Python中,字典类型基于哈希表实现,可用于管理文件元数据、缓存机制、版本控制及快速搜索等功能,极大提升工作效率和数据安全性。
74 0
|
2月前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
95 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
1天前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
1天前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
|
1天前
|
存储 程序员 C语言
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
4天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
70 19
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
52 13