当前位置: 首页 > news >正文

用单调栈高效解决 “首尾均为最大值” 的子数组计数问题(Leetcode 3113)

这几天国庆有点疯,没什么好更新的博客,刚好今天刷到了这个题目,单调栈的典型应用,所以来写一篇博客记录一下(QWQ).

本题链接,大家可以先做一下

3113. 边界元素是最大值的子数组数目 - 力扣(LeetCode)

一、题目描述:明确问题边界

给定一个正整数数组 nums,请统计其中有多少个子数组满足:子数组的第一个元素和最后一个元素,都是该子数组中的最大值

示例理解

  • 输入:nums = [2, 1, 2]符合条件的子数组:[2](首尾都是 2,最大值 2)、[1](首尾都是 1,最大值 1)、[2](首尾都是 2,最大值 2)、[2,1,2](首尾都是 2,最大值 2)→ 共 4 个。
  • 输入:nums = [3, 1, 3, 3]符合条件的子数组:4 个单个元素 + [3,1,3] + [3,1,3,3] + [3,3] → 共 7 个。

二、核心思路:为什么用单调栈?

要解决这个问题,首先需要拆解 “首尾均为最大值” 的核心约束:

  1. 单个元素子数组必符合条件:每个元素自身组成的子数组(长度 1),首尾都是自己,天然满足 “最大值” 条件,这是基础计数(共n个,n为数组长度)。
  2. 长度≥2 的子数组需满足两点
    • 首尾元素相等(设为x):若首尾元素不等,假设首元素 > 尾元素,则尾元素不是最大值;反之同理,因此首尾必须相等。
    • 子数组内所有元素≤x:确保x是子数组的最大值,避免中间出现更大元素破坏约束。

单调栈的价值

直接暴力枚举所有子数组(O (n²))会超时,而非递增单调栈能高效维护 “元素≤当前处理元素” 的序列,同时记录 “有效子数组计数”,将时间复杂度降至 O (n)。

栈中存储的是(元素值, 计数)的二元组,其中:

  • 元素值:维护非递增序列,确保栈内元素≥当前处理元素(满足 “子数组内元素≤首尾”)。
  • 计数:以该元素为结尾的、符合条件的子数组数量(用于递推统计首尾相等的有效子数组)。

三、代码实现与逐行解析

以下是基于单调栈的最优解法(即用户原创的正确代码),我们逐行拆解其逻辑:

cpp

运行

#include <vector>
#include <stack>
using namespace std;class Solution {
public:long long numberOfSubarrays(vector<int>& nums) {long long ans = 0;  // 结果(用long long避免溢出)// 栈存储:<元素值, 以该元素为结尾的符合条件子数组数量>stack<pair<int, int>> st;for (auto& e : nums) {  // 遍历每个元素int size = 1;  // 初始:单个元素自身的子数组(长度1)// 步骤1:弹出栈中比当前元素小的元素// 原因:这些元素无法与e组成有效子数组(e更大,它们不是最大值)while (!st.empty() && st.top().first < e) {st.pop();}// 步骤2:处理与栈顶相等的元素(首尾相等的有效子数组)if (!st.empty() && st.top().first == e) {ans += st.top().second;  // 累加之前的有效子数组数量size += st.top().second; // 递推更新当前元素的计数st.pop();  // 弹出旧计数,避免重复统计}// 步骤3:统计单个元素的子数组(基础计数)ans++;// 步骤4:将当前元素及其计数压入栈,维护非递增序列st.push({e, size});}return ans;}
};

四、测试用例验证:代码正确性

我们用 3 个典型测试用例验证代码逻辑,确保覆盖不同场景:

测试用例 1:混合场景 nums = [2, 1, 2]

  1. 处理第一个2
    • 栈空,size=1ans++(ans=1),压入(2, 1)
  2. 处理1
    • 栈顶2>1,不弹出,size=1ans++(ans=2),压入(1, 1)
  3. 处理第二个2
    • 弹出(1,1)(1<2);
    • 栈顶2==2ans +=1(ans=3,对应子数组[2,1,2]),size=1+1=2,弹出(2,1)
    • ans++(ans=4,对应单个元素2),压入(2, 2)
  4. 最终结果:4(正确)。

测试用例 2:连续相等元素 nums = [3, 3, 3]

  1. 处理第一个3ans=1,压入(3,1)
  2. 处理第二个3
    • 栈顶3==3ans +=1(ans=2,对应[3,3]),size=2,弹出(3,1)
    • ans++(ans=3),压入(3,2)
  3. 处理第三个3
    • 栈顶3==3ans +=2(ans=5,对应[3,3][3,3,3]),size=3,弹出(3,2)
    • ans++(ans=6),压入(3,3)
  4. 最终结果:6(正确,对应 3 个单个元素 + 2 个长度 2 子数组 + 1 个长度 3 子数组)。

测试用例 3:严格递增序列 nums = [1, 2, 3]

  1. 处理1ans=1,压入(1,1)
  2. 处理2
    • 弹出(1,1)(1<2);
    • 栈空,ans++(ans=2),压入(2,1)
  3. 处理3
    • 弹出(2,1)(2<3);
    • 栈空,ans++(ans=3),压入(3,1)
  4. 最终结果:3(正确,仅单个元素子数组符合条件)。

五、复杂度分析:为什么高效?

  • 时间复杂度 O (n):每个元素最多入栈 1 次、出栈 1 次,栈操作的总次数为 O (n),遍历数组也是 O (n),整体线性。
  • 空间复杂度 O (n):最坏情况下(数组严格非递增),栈存储所有元素,空间为 O (n);最好情况下(严格递增),栈内最多 1 个元素,空间为 O (1)。

六、总结与拓展

代码的巧妙之处

  1. 非递增栈维护约束:确保栈内元素≥当前元素,天然满足 “子数组内元素≤首尾” 的条件。
  2. 计数递推减少重复:栈中 “计数” 记录了历史有效子数组数量,遇到相等元素时直接累加,避免暴力枚举。
  3. 覆盖所有场景:同时统计单个元素和长度≥2 的子数组,无漏算、无错算。

类似问题拓展

若遇到 “子数组最大值相关” 的统计问题(如 “以每个元素为最大值的子数组数量”),均可尝试用单调栈维护 “元素大小关系”,核心是通过栈记录元素的左右边界,再结合计数逻辑求解。

通过本文的分析,我们可以看到:好的算法不仅要 “正确”,更要 “高效”。单调栈作为处理 “数组最大值 + 子数组” 问题的利器,能帮我们突破暴力解法的瓶颈,而理解其背后的 “约束维护” 逻辑,才是掌握这类问题的关键。

大家可以直接跳转至灵神的单调栈题单继续学习更多关于单调栈的有趣用法哦

分享|【算法题单】单调栈(矩形面积/贡献法/最小字典序) - 讨论 - 力扣(LeetCode)

http://www.dtcms.com/a/457858.html

相关文章:

  • 企业网站自己可以做吗wordpress 登陆 插件
  • 初学c#-c#和.NET Framework - onecopper
  • 大沥南庄网站建设网站开发建设流程
  • nvMolKit:一套基于GPU加速的RDKit核心函数集
  • LOBE-GS:分块致密化效率提升
  • 福州建设招聘信息网站pt网站怎么下载与做
  • dede免费手机网站模板象山seo的优化
  • 央视支持新消费模式:积分助力商家锁客,复购率翻倍
  • 专业政务软件开发北京移动端网站优化
  • 怎样提高网站访问速度一起做网站欧洲站
  • 公司网站建设费属于宣传费吗重庆专业的网站建设公司
  • 广西网站建设哪里好wordpress消息通知
  • 海南网站建设网站开发网站建设的流程步骤
  • 吴恩达机器学习课程(PyTorch适配)学习笔记:1.5 决策树与集成学习
  • 网站推广营销方法成都网站建设 致尚
  • 常州网站建设网站做网站必须要加v吗
  • 理想星环 OS 深度解析:开源浪潮下的智能汽车操作系统革命
  • Qt开发的应用程序编译链接Fast DDS库
  • 网站开发人员属于旅行社手机网站建设成
  • VBA经典应用69例应用9:Preserve关键字利用,保留原值
  • WebRTC 入门与实战(三)之高级篇
  • 二:RAG 的 “语义密码”:向量、嵌入模型与 Milvus 向量数据库实操
  • 一元夺宝网站建设企业网站建设方案对比汇报
  • 《道德经》第十二章
  • 点击图片进入网站要怎么做做网站老板嫌弃太丑谁的锅
  • 如何在ubuntu20.04配置动态壁纸和bongoCat
  • 网站建设中外链与内链的技巧三视觉平面设计网
  • 小公司要不要建设网站网页打不开怎么处理
  • p2vr做的网站怎么再次打开163企业邮箱免费版
  • deepseek vs 元宝--人工智能还是人工智障?