leetcode 862. 和至少为 K 的最短子数组
这段代码使用了前缀和+单调队列的组合策略来高效解决"和至少为K的最短子数组"问题。我将从问题定义、核心思路到代码实现逐步拆解:
问题定义
给定数组 nums
和整数 k
,找到和 ≥k 的最短非空子数组,返回其长度。
示例:nums = [2,-1,2]
, k = 3
→ 子数组 [2,-1,2]
和为3,长度3,返回3。
核心思路
1. 前缀和数组
前缀和数组 prefix[i]
表示 nums
前 i
个元素的和。
- 作用:快速计算子数组和。子数组
nums[i..j]
的和 =prefix[j+1] - prefix[i]
。 - 示例:
nums = [2,-1,2]
→prefix = [0, 2, 1, 3]
。
子数组[2,-1]
的和 =prefix[2] - prefix[0] = 1 - 0 = 1
。
2. 单调队列的作用
单调队列 q
存储前缀和数组的下标,确保队列中的下标对应的前缀和严格递增。
- 目标:对于每个右边界
j
,快速找到满足prefix[j] - prefix[i] ≥k
的最大左边界i
(使子数组长度j-i
最小)。 - 优化逻辑:
- 淘汰不可能的左边界:若存在
i1 < i2
且prefix[i1] ≥ prefix[i2]
,则i1
永远不可能是最优解(因为i2
更靠右且前缀和更小,使差值更大)。 - 单调性加速查询:队列中前缀和递增,若队首
i
不满足条件,则后续元素更不可能满足,直接停止检查。
- 淘汰不可能的左边界:若存在
代码实现解析
int shortestSubarray(vector<int>& nums, int k) {int n = nums.size();// 计算前缀和数组vector<long long> prefix(n + 1, 0);for (int i = 0; i < n; ++i) {prefix[i + 1] = prefix[i] + nums[i];}deque<int> q; // 存储前缀和数组的下标,按prefix值单调递增int ans = INT_MAX;for (int j = 0; j <= n; ++j) {// 移除队尾较大的元素,保持队列单调性while (!q.empty() && prefix[j] <= prefix[q.back()]) {q.pop_back();}// 检查队首是否满足条件,更新最短长度while (!q.empty() && prefix[j] - prefix[q.front()] >= k) {ans = min(ans, j - q.front());q.pop_front(); // 队首已经找到最优解,后续无需再考虑}q.push_back(j); // 将当前下标加入队列}return ans == INT_MAX ? -1 : ans;
}
关键步骤详解
-
前缀和计算:
prefix[i+1] = prefix[i] + nums[i]
,确保prefix[j+1] - prefix[i]
表示子数组nums[i..j]
的和。 -
维护单调队列:
- 移除队尾较大元素:若
prefix[j] ≤ prefix[q.back()]
,则弹出队尾。
目的:保持队列单调性,确保后续查询时队首是最小前缀和。 - 检查队首条件:若
prefix[j] - prefix[q.front()] ≥k
,则更新最短长度并弹出队首。
原理:队首是当前最小前缀和,若其不满足条件,则后续元素更不可能满足;若满足,则队首是当前最优左边界(对应最短长度),弹出后不再考虑。
- 移除队尾较大元素:若
-
添加当前下标:
将j
加入队列尾部,确保队列中的下标按前缀和递增。
示例演示
输入:nums = [2,-1,2]
, k = 3
前缀和:prefix = [0, 2, 1, 3]
j | prefix[j] | 队列 q | 操作 | 最短长度 |
---|---|---|---|---|
0 | 0 | [0] | 初始化队列 | ∞ |
1 | 2 | [0, 1] | 2-0=2<3,加入1 | ∞ |
2 | 1 | [0, 2] | 弹出1(1≥2),加入2 | ∞ |
3 | 3 | [2, 3] | 3-0=3≥3 → 长度3-0=3 | 3 |
复杂度分析
- 时间复杂度:O(n)。每个元素最多入队和出队一次。
- 空间复杂度:O(n)。队列最坏情况下存储所有下标。
关键点总结
- 前缀和:将子数组和转化为前缀和的差值,O(1)时间计算。
- 单调队列:
- 单调性:确保队列中的前缀和递增,队首是最小前缀和。
- 淘汰机制:移除不可能的左边界,加速查询。
- 队首弹出逻辑:一旦队首满足条件,其对应的是当前最短长度,后续无需再考虑,直接弹出。
这种方法高效解决了子数组和的优化问题,是处理类似问题的经典策略。
在这段代码中,使用 while
循环而非 if
是由单调队列的性质和问题的最优解特性共同决定的。以下是详细分析:
一、为什么必须用 while
循环?
场景假设:
假设队列 q
中存储的前缀和下标为 [i1, i2, i3]
,对应的前缀和满足 prefix[i1] < prefix[i2] < prefix[i3]
(队列递增)。
当遍历到 j
时,prefix[j] - prefix[i1] ≥k
,说明:
i1
是可行的左边界,对应子数组长度为j - i1
。- 由于
prefix[i2] > prefix[i1]
,则prefix[j] - prefix[i2] < prefix[j] - prefix[i1]
,但可能仍≥k
。
例如:prefix[j]=10
,prefix[i1]=2
,prefix[i2]=5
,k=3
→10-2=8≥3
,10-5=5≥3
。
结论:队首 i1
满足条件时,后续的 i2
、i3
可能也满足条件,且对应的子数组长度更短(因为 i2 > i1
,j-i2 < j-i1
)。
因此需要持续检查队首之后的元素,直到找到不满足条件的队首,才能保证不会遗漏更优解。
二、while
循环的核心作用
1. 找到所有可行的左边界
队列中的前缀和递增,因此当 prefix[j] - prefix[q.front()] ≥k
时:
- 队首是当前最小的前缀和,对应最大的可行左边界
q.front()
(子数组长度最短)。 - 但后续元素的前缀和更大,可能仍满足条件,且对应更短的子数组。
示例:
prefix = [0, 1, 3, 5]
, k=2
, j=3
(prefix[j]=5
)。
- 队首
i=0
:5-0=5≥2
→ 长度3-0=3。 - 弹出队首后,新队首
i=1
:5-1=4≥2
→ 长度3-1=2(更优)。 - 弹出队首后,新队首
i=2
:5-3=2≥2
→ 长度3-2=1(最优)。 - 最终队列为
[3]
,循环停止。
若用 if
仅检查一次队首,会漏掉后续更优的解(如长度2和1)。
2. 维护队列的单调性和有效性
每次弹出队首后,新的队首可能仍满足条件,需要继续检查:
- 队首一旦不满足条件,后续元素的前缀和更大,差值更小,必然不满足条件,循环可以终止。
- 队首满足条件时,弹出队首是安全的,因为:
- 该队首对应的子数组长度是当前可行解中最短的(因为队首是最大的左边界)。
- 后续的左边界
i
更大(i > q.front()
),对应的子数组长度更小,可能更优。
三、若用 if
会发生什么?
错误示例:
// 错误:用if替代while
if (!q.empty() && prefix[j] - prefix[q.front()] >= k) {ans = min(ans, j - q.front());q.pop_front();
}
场景:
prefix = [0, 2, 3]
, k=2
, j=2
(prefix[j]=3
)。
- 队首
i=0
:3-0=3≥2
→ 记录长度2-0=2,弹出队首。 - 此时队列变为
[1]
,prefix[1]=2
,3-2=1<2
,不满足条件。 - 正确解:子数组
[2]
(下标1-1),和为2,长度1。 - 错误原因:用
if
仅检查初始队首,未发现后续队首i=1
可能满足条件(虽然本例中不满足,但存在其他情况)。
结论:if
只能处理队首的单次检查,无法处理队列中多个连续满足条件的元素,导致遗漏更优解。
四、代码中的逻辑正确性
while (!q.empty() && prefix[j] - prefix[q.front()] >= k) {ans = min(ans, j - q.front()); // 记录当前最优解(可能不是全局最优)q.pop_front(); // 弹出队首,检查下一个元素
}
- 循环条件:只要队首满足条件,就继续检查。
- 操作顺序:先记录解,再弹出队首。
- 因为队首是当前最大的左边界,对应的长度最短,弹出后新队首可能更优(长度更短)。
- 例如:队首
i1
对应长度L1
,下一个队首i2 > i1
对应长度L2 < L1
,必须记录L2
。
五、总结:while
的必要性
- 处理多个连续可行解:队列中可能存在多个满足条件的左边界,
while
确保遍历所有可能。 - 利用单调性剪枝:一旦队首不满足条件,后续元素必然不满足,直接终止循环,不会增加额外开销。
- 确保最优解:通过持续弹出队首,每次记录当前最短长度,最终得到全局最优解。
因此,while
循环是该算法正确性的关键,不能用 if
替代。
相关文章:
leetcode 862. 和至少为 K 的最短子数组
这段代码使用了前缀和单调队列的组合策略来高效解决"和至少为K的最短子数组"问题。我将从问题定义、核心思路到代码实现逐步拆解: 问题定义 给定数组 nums 和整数 k,找到和 ≥k 的最短非空子数组,返回其长度。 示例:n…...

CodeBuddy 实现图片转素描手绘工具
本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 前言 最近在社交媒体上,各种素描风格的图片火得一塌糊涂,身边不少朋友都在分享自己的 “素描照”,看着那些黑白线条勾勒出的独特韵味&a…...

3.8.2 利用RDD计算总分与平均分
在本次实战中,我们利用Spark的RDD完成了成绩文件的总分与平均分计算任务。首先,准备了包含学生成绩的文件并上传至HDFS。接着,通过交互式方式逐步实现了成绩的读取、解析、总分计算与平均分计算,并最终输出结果。此外,…...

29-FreeRTOS事件标志组
一、概述 事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。 即一个任务可以等待多个事件的发生࿱…...
天地图实景三维数据分享(江苏)
1、天地图介绍 “天地图”(MAPWORLD)是国家地理信息公共服务平台 ,2011年正式上线 ,是自然资源部门向社会提供各类在线地理信息公共服务、推动地理信息数据开放共享的政府网站 ;是中国区域内基础地理信息数据资源最全…...
Jenkins的Pipline中有哪些区块,以及其它知识点整理
目录 ■模板 ■Jenkins的Pipline中有哪些区块 1. pipeline(顶层区块) 2. agent(执行节点) 3. stages(阶段集合) 4. stage(单个阶段) 5. steps(具体步骤࿰…...

「EMD/EEMD/VMD 信号分解方法 ——ECG信号处理-第十四课」2025年5月23日
一、引言 上一节,我们介绍了希尔伯特黄变换(HHT)及其经验模态分解(EMD)的相关内容,这一节,我们继续拓展EMD分解技术,补充介绍集合经验模态分解(Ensemble Empirical Mode …...

二叉树层序遍历6
INT_MIN的用法: INT_MIN是C/C 中的一个宏常量 ,在 <limits.h> (C 中也可使用 <climits> )头文件中定义,代表 int 类型能表示的最小整数值 。其用法主要体现在以下方面: 1.初始化变量 …...

【论文精读】2023 AAAI--FastRealVSR现实世界视频超分辨率(RealWorld VSR)
文章目录 一、摘要二、Method2.1 现象(问题)--对应文中隐状态的分析(Analysis of Hidden State)2.2 怎么解决 --对应文中Framework2.2.1 整体流程:2.2.2 HSA模块怎么工作?2.2.2.1 隐藏状态池2.2.2.2 选择性…...

IPython 常用魔法命令
文章目录 IPython 魔法命令(Magic Commands)一、系统与文件操作1. %ls2. %cd和%pwd3. %%writefile4. %run 二、性能分析与计时1. %timeit2. %prun3. %%timeit 三、代码处理与交互1. %load2. %edit3. %store 四、调试与诊断2. …...
数据同步自动化——如何用Python打造高效工具?
友友们好! 我是Echo_Wish,我的的新专栏《Python进阶》以及《Python!实战!》正式启动啦!这是专为那些渴望提升Python技能的朋友们量身打造的专栏,无论你是已经有一定基础的开发者,还是希望深入挖掘Python潜力的爱好者,这里都将是你不可错过的宝藏。 在这个专栏中,你将会…...
开源与闭源之争:AI时代的创新博弈与未来抉择
在人工智能技术狂飙突进的今天,开源与闭源之争已不再局限于技术圈的讨论,而是演变为一场关乎技术伦理、商业格局乃至人类文明走向的深度博弈。当Meta的Llama 3开源模型下载量突破百万,当OpenAI的GPT-5继续加固技术壁垒,这场没有硝…...
flutter dart class语法说明、示例
🔹 Dart 中的 class 基本语法 class ClassName {// 属性(字段)数据类型 属性名;// 构造函数ClassName(this.属性名);// 方法返回类型 方法名() {// 方法体} }✅ 示例:创建一个简单的 Person 类 class Person {// 属性String name;…...

Java虚拟机 - 程序计数器和虚拟机栈
运行时数据结构 Java运行时数据区程序计数器为什么需要程序计数器执行流程虚拟机栈虚拟机栈作用虚拟机栈核心结构运行机制 Java运行时数据区 首先介绍Java运行时数据之前,我们要了解,对于计算机来说,内存是非常重要的资源,因为内…...
SpringMVC04所有注解按照使用位置划分| 按照使用层级划分(业务层、视图层、控制层)
目录 一、所有注解按照使用位置划分(类、方法、参数) 1. 类级别注解 2. 方法级别注解 3. 参数级别注解 4. 字段/返回值注解 二、按照使用层级划分(业务层、视图层、控制层) 1、控制层(Controller Layer&#x…...

新能源汽车产业链图谱分析
1. 产业定义 新能源汽车是指采用非常规的车用燃料作为动力来源,综合车辆的动力控制和驱动方面的先进技术,形成的具有新技术、新结构、技术原理先进的汽车。 新能源车包括四大类型:混合动力电动汽车(HEV)、纯电动汽车…...

如何在PyCharm2025中设置conda的多个Python版本
前言 体验的最新版本的PyCharm(Community)2025.1.1,发现和以前的版本有所不同。特别是使用Anaconda中的多个版本的Python的时候。 关于基于Anaconda中多个Python版本的使用,以及对应的Pycharm(2023版)的使用,可以参考…...
005 深度优先搜索(DFS)算法详解:图解+代码+经典例题
📌 什么是深度优先搜索? 深度优先搜索(Depth-First Search,DFS)是算法竞赛和面试中最高频的暴力搜索算法之一。其核心思想是“一条路走到黑”,从起点出发,优先探索最深的节点,直到无…...

maven快速上手
之前我们项目如果要用到其他额外的jar包,需要自己去官网下载并且导入。但是有maven后,直接在maven的pom.xml文件里用代码配置即可,配置好后maven会自动帮我们联网下载并且会自动导入该jar包 在右边的maven中,我们可以看到下载安装…...

cplex12.9 安装教程以及下载
cplex 感觉不是很好找,尤其是教育版,我这里提供一个版本,在下面的图可以看到,不仅可以配置matlab,也可以配置vs,现在拿vs2017来测试一下,具体文件的文件有需要的可以复制下面的链接获取 我用网盘分享了「c…...

甘特图实例 dhtmlxGantt.js
本文介绍了如何使用dhtmlxGantt库创建一个基础的甘特图示例,并对其进行汉化和自定义配置。首先,通过引入dhtmlxgantt.css和dhtmlxgantt.js文件初始化甘特图。接着,通过设置gantt.i18n.setLocale("cn")实现核心文本的汉化࿰…...
AMD硬件笔试面试题型解析
本专栏预计更新60期左右。当前第12期 这个系列通过在各类网上搜索大厂公开的笔试和面试题目,然后构造相关的知识点矩阵,让大家对核心的知识点有更深的认识,这个过程虽然耗时费力,但大厂的很多题目确实非常巧妙,很有代表性。由于官方没有发布过这样的题库,所以文章中的题目…...

视频剪辑 VEGAS - 配置视频片段保持原长宽比
VEGAS 配置视频片段保持原长宽比 右击视频片段 -> 选择【开关】 -> 勾选【保持长宽比】 右击视频片段 -> 点击【属性】 -> 弹出【属性】窗口 点击【媒体】 -> 选择【像素宽高比】为【1,0000(方形)】...

力扣 54 .螺旋矩阵
文章目录 题目介绍题解 题目介绍 题解 代码如下: class Solution {public List<Integer> spiralOrder(int[][] matrix) {List<Integer> res new ArrayList<>();if (matrix.length 0){return res;}int l 0, r matrix[0].length - 1, t 0, b…...

四、【API 开发篇 (上)】:使用 Django REST Framework 构建项目与模块 CRUD API
【API 开发篇 】:使用 Django REST Framework 构建项目与模块 CRUD API 前言为什么选择 Django REST Framework (DRF)?第一步:创建 Serializers (序列化器)第二步:创建 ViewSets (视图集)第三步:配置 URLs (路由)第四步…...
python使用pycharm和conda 设置默认使用清华镜像
将步骤分为Conda配置和PyCharm配置两部分。Conda部分包括添加镜像源、调整优先级、更新环境。PyCharm部分需要根据版本说明如何添加镜像源到项目解释器设置中。同时,需要验证配置是否成功,并提醒常见问题,比如路径错误或缓存问题。需要确保引…...
Prometheus+Grafana实现对服务的监控
PrometheusGrafana实现对服务的监控 前言:PrometheusGrafana实现监控会更加全面,监控的组件更多 Prometheus官网 https://prometheus.io/docs/prometheus/latest/getting_started/ Grafana官网 https://grafana.com/docs/ 一、安装PrometheusGrafana 这…...

ARM笔记-ARM伪指令及编程基础
第四章 ARM伪指令及编程基础 4.1 伪指令概述 4.1.1 伪指令定义 人们设计了一些专门用于指导汇编器进行汇编工作的指令,由于这些指令不形成机器码指令,它们只是在汇编器进行汇编工作的过程中起作用,所以被叫做伪指令。 4.1.2 伪指令特征 …...

Python入门手册:Python基础语法
Python是一种简洁、易读且功能强大的编程语言,非常适合初学者入门。无论你是编程新手,还是有一定编程基础但想学习Python的开发者,掌握Python的基础语法都是迈向高效编程的第一步。本文将详细介绍Python的基本语法,包括变量和数据…...
SpringBoot-SpringBoot源码解读
SpringBoot-SpringBoot源码解读 一、Spring Boot启动过程概述 Spring Boot通过一系列的类和机制,简化了Spring应用的启动流程。当你执行SpringApplication.run()时,Spring Boot会自动完成应用的初始化、环境配置、组件加载、自动配置等任务,…...