C++前缀和算法应用:和至少为 K 的最短子数组的原理、源码及测试用例

简介: C++前缀和算法应用:和至少为 K 的最短子数组的原理、源码及测试用例

本文涉及的基础知识点

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

题目

给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。

子数组 是数组中 连续 的一部分。

示例 1:

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

输出:1

示例 2:

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

输出:-1

示例 3:

输入:nums = [2,-1,2], k = 3

输出:3

提示:

1 <= nums.length <= 105

-105 <= nums[i] <= 105

1 <= k <= 10^9

#分析

时间复杂度

枚举子数组的结尾i,时间复杂度O(n),利用二分查找,每次枚举O(logn),故总时间复杂度是O(nlogn)。

细节

llSun是num[0,i]的和,vSumIndex 记录[0,j]之和,j取值[-1,i)。假定j0 < j1,且sum[j0] >= sum[j1],那sum[j0,i]小于sum[j1,i]且j0的长度大于j1,所以j0一定不是备选答案,可淘汰。淘汰后如果j0<j1,则sum[j0]一定小于sum[j1]。也就是前缀和和索引都是按升

序排序。sum-sumold >=k ==> sum-k>=sumold ==>sumold <= sum-k 。淘汰的时候:由于是按升序排序,所以sum[j1]不大于等于sum-k,那么sum[j0]也一定不大于等于sum-k。所以找到一个不符合,就可停止了。

#核心代码

class Solution {
public:
int shortestSubarray(vector& nums, int k) {
m_c = nums.size();
m_vRet.assign(m_c, -1);
vector<pair<long long, int>> vSumIndex = { {0,-1} };
long long llSum = 0;
m_vRet.assign(m_c, INT_MAX);
for (int i = 0; i < m_c; i++)
{
llSum += nums[i];
//sum-sumold >=k ==> sum-k>=sumold ==>sumold <= sum-k
//由于sum和index都是升序,所以可以二分
auto it = std::upper_bound(vSumIndex.begin(), vSumIndex.end(), llSum - k, []( const long long ll,const pair<long, int>& pi)
{
return ll < pi.first;
});
if (vSumIndex.begin() != it)
{
m_vRet[i] = i - std::prev(it)->second;
}
while (vSumIndex.size() && (vSumIndex.back().first >= llSum))
{
vSumIndex.pop_back();
}
vSumIndex.emplace_back(llSum, i);
}
const int iRet = *std::min_element(m_vRet.begin(), m_vRet.end());
return (INT_MAX == iRet) ? -1 : iRet;
}
int m_c;
vector m_vRet;
};

测试用例

m_vRet是多余的,是为了方便排错。

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);
}
class Solution {
public:
int shortestSubarray(vector& nums, int k) {
m_c = nums.size();
m_vRet.assign(m_c, -1);
vector<pair<long long, int>> vSumIndex = { {0,-1} };
long long llSum = 0;
m_vRet.assign(m_c, INT_MAX);
for (int i = 0; i < m_c; i++)
{
llSum += nums[i];
//sum-sumold >=k ==> sum-k>=sumold ==>sumold <= sum-k
//由于sum和index都是升序,所以可以二分
auto it = std::upper_bound(vSumIndex.begin(), vSumIndex.end(), llSum - k, []( const long long ll,const pair<long, int>& pi)
{
return ll < pi.first;
});
if (vSumIndex.begin() != it)
{
m_vRet[i] = i - std::prev(it)->second;
}
while (vSumIndex.size() && (vSumIndex.back().first >= llSum))
{
vSumIndex.pop_back();
}
vSumIndex.emplace_back(llSum, i);
}
const int iRet = *std::min_element(m_vRet.begin(), m_vRet.end());
return (INT_MAX == iRet) ? -1 : iRet;
}
int m_c;
vector m_vRet;
};

错误做法

auto it = std::upper_bound(vSumIndex.begin(), vSumIndex.end(), std::make_pair(llSum - k,0));

我们期望:

返回第一个 first大于llSum-k的值。

实际上,返回第一个符合以下条件之一的迭代器:

一,first大于llSum-k

二,first等于llSum-k,second>0

解决方法:将0换成m_c,这样条件二,就永远不会成立。也可以换成INT_MAX。修改后的代码如下:

auto it = std::upper_bound(vSumIndex.begin(), vSumIndex.end(), std::make_pair(llSum - k,m_c));

2023年3月分的旧版

仅供参考

template
bool Less(const std::pair<Class1, int>& p, Class1 iData)
{
return p.first < iData;
}
template
bool Greater(const std::pair<Class1, int>& p, Class1 iData)
{
return p.first > iData ;
}
class Solution {
public:
int shortestSubarray(vector& nums, int k) {
int iMinLen = INT_MAX;
std::vector<std::pair<long, int>> vQue;
vQue.emplace_back(0, -1);
long long llSum = 0;
for (int i = 0; i < nums.size(); i++)
{
llSum += nums[i];
int iLessEqualNum = std::lower_bound(vQue.begin(), vQue.end(), llSum - k + 1, Less) - vQue.begin();
if (iLessEqualNum > 0 )
{
iMinLen = min(iMinLen, i - vQue[iLessEqualNum - 1].second);
}
while (vQue.size() && (llSum <= vQue.back().first))
{
vQue.pop_back();
}
vQue.emplace_back(llSum, i);
}
return (INT_MAX == iMinLen) ? -1 : iMinLen;
}
};

扩展阅读

视频课程

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


相关文章
|
12月前
|
存储 算法
算法入门:专题二---滑动窗口(长度最小的子数组)类型题目攻克!
给定一个正整数数组和目标值target,找出总和大于等于target的最短连续子数组长度。利用滑动窗口(双指针)优化,维护窗口内元素和,通过单调性避免重复枚举,时间复杂度O(n)。当窗口和满足条件时收缩左边界,更新最小长度,最终返回结果。
|
搜索推荐 编译器 C语言
【C++核心】特殊的元素集合-数组与字符串详解
这篇文章详细讲解了C++中数组和字符串的基本概念、操作和应用,包括一维数组、二维数组的定义和使用,以及C风格字符串和C++字符串类的对比。
629 5
|
算法 数据可视化 数据挖掘
基于EM期望最大化算法的GMM参数估计与三维数据分类系统python源码
本内容展示了基于EM算法的高斯混合模型(GMM)聚类实现,包含完整Python代码、运行效果图及理论解析。程序使用三维数据进行演示,涵盖误差计算、模型参数更新、结果可视化等关键步骤,并附有详细注释与操作视频,适合学习EM算法与GMM模型的原理及应用。
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
297 0
|
存储 人工智能 算法
C 408—《数据结构》算法题基础篇—数组(通俗易懂)
408考研——《数据结构》算法题基础篇之数组。(408算法题的入门)
1153 23
|
存储 监控 算法
关于员工上网监控系统中 PHP 关联数组算法的学术解析
在当代企业管理中,员工上网监控系统是维护信息安全和提升工作效率的关键工具。PHP 中的关联数组凭借其灵活的键值对存储方式,在记录员工网络活动、管理访问规则及分析上网行为等方面发挥重要作用。通过关联数组,系统能高效记录每位员工的上网历史,设定网站访问权限,并统计不同类型的网站访问频率,帮助企业洞察员工上网模式,发现潜在问题并采取相应管理措施,从而保障信息安全和提高工作效率。
316 7
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
4443 1
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
558 5
|
算法 程序员 索引
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器
栈的基本概念、应用场景以及如何使用数组和单链表模拟栈,并展示了如何利用栈和中缀表达式实现一个综合计算器。
465 1
数据结构与算法学习七:栈、数组模拟栈、单链表模拟栈、栈应用实例 实现 综合计算器