2025.11.10 力扣每日一题
3542.将所有元素变为0的最少操作次数
class Solution {
public:int minOperations(vector<int>& nums) {vector<int> s;int res = 0;for(int a : nums){while(!s.empty() && s.back() > a){s.pop_back();}if( a==0) continue;if(s.empty() || s.back() < a){++res;s.push_back(a);}}return res;}
};
单线栈知识点:
单调栈是一种特殊的栈数据结构,其核心特性是:栈内的元素始终保持单调递增或单调递减的顺序。这种特性使得它在解决 “找下一个更大 / 更小元素”“区间最值” 等问题时效率极高(时间复杂度通常为 O (n))。
核心概念
- 单调性:栈内元素从栈底到栈顶严格遵循递增(或递减)规则。
- 操作原则:当新元素入栈时,若破坏单调性,则弹出栈顶元素,直到栈满足单调性后再入栈。
- 用途:高效寻找数组中每个元素的 “下一个更大元素”“上一个更小元素” 等,避免暴力遍历(O (n²))。
分类
-
单调递增栈:栈内元素从栈底到栈顶从小到大排列。例如:
[1, 3, 5, 7] -
单调递减栈:栈内元素从栈底到栈顶从大到小排列。例如:
[7, 5, 3, 1]
工作流程(以单调递增栈为例)
假设数组为 [2, 1, 3],模拟入栈过程:
- 元素
2入栈,栈为[2](满足递增)。 - 新元素
1比栈顶2小,破坏递增性,弹出2,栈为空后入栈1,栈为[1]。 - 新元素
3比栈顶1大,直接入栈,栈为[1, 3](满足递增)。
最终栈保持递增,且过程中通过弹出元素可记录 “被弹出元素的下一个更大元素是当前新元素”(如 2 的下一个更大元素是 3)。
典型应用
-
下一个更大元素问题:对数组中每个元素,找到右侧第一个比它大的元素,若不存在则为
-1。示例:nums = [2, 1, 2, 4, 3]答案:[4, 2, 4, -1, -1]解法:用单调递减栈,遍历数组时,弹出栈顶元素并记录当前元素为其 “下一个更大元素”。 -
接雨水问题:计算数组中 “凹槽” 能接住的雨水量(如
[0,1,0,2,1,0,1,3,2,1,2,1]答案为6)。解法:用单调递减栈,栈内存储 “凹槽左边界”,遇到更高元素时计算凹槽面积。 -
柱状图中最大的矩形问题:找到柱状图中能容纳的最大矩形面积(如
[2,1,5,6,2,3]答案为10)。解法:用单调递增栈,栈内存储柱子索引,遇到更矮柱子时弹出并计算以弹出柱子为高的最大矩形。
优势
- 时间效率:每个元素最多入栈和出栈一次,总时间复杂度为 O (n)。
- 简洁性:通过栈的单调性,可在遍历过程中同步记录所需信息,避免嵌套循环。
总结
单调栈的核心是利用 “栈的单调性” 过滤无效元素,只保留对后续计算有价值的元素,从而高效解决各类区间相关问题。掌握它的关键是理解 “何时入栈、何时出栈” 以及 “弹出元素时能获取哪些信息”。
代码应用:
这段代码是用来解决 “将所有元素变为 0 的最少操作次数” 问题的,其核心思路是利用单调递增栈来统计需要的最少操作次数,与题目中 “每次操作可选择一个连续子数组,将其中最小的非负整数设为 0” 的规则相契合。
代码逐行解释
-
vector<int> s;定义一个单调递增栈,栈中存储的是 “当前需要处理的最小非负整数的递增序列”。 -
int res = 0;用于记录最少操作次数。 -
for(int a : nums)遍历数组中的每个元素。 -
while(!s.empty() && s.back() > a)这是单调栈的核心操作:- 当栈不为空,且 ** 栈顶元素大于当前元素
a** 时,弹出栈顶元素。 - 目的是维护栈的单调递增性,确保栈中只保留 “当前未被覆盖的最小非负整数”。
- 当栈不为空,且 ** 栈顶元素大于当前元素
-
if( a==0) continue;如果当前元素是 0,无需处理(因为目标就是将所有元素变为 0,0 本身已经满足),直接跳过。 -
if(s.empty() || s.back() < a)当栈为空,或 ** 栈顶元素小于当前元素a** 时:++res;:操作次数加 1(因为需要对当前元素a执行一次新的操作)。s.push_back(a);:将当前元素a入栈,维护单调递增性。
逻辑本质
代码的核心是按 “最小非负整数的递增顺序” 统计操作次数:
- 每次遇到一个新的 “未被更小元素覆盖的非 0 元素” 时,就需要一次操作来将其变为 0。
- 单调栈确保了 “只有比栈中所有元素更大的非 0 元素” 才会触发新的操作,从而保证操作次数最少。
示例验证(以nums = [1,2,1,2,1,2]为例)
遍历过程:
a=1:栈空,res=1,栈变为[1]。a=2:栈顶1 < 2,res=2,栈变为[1,2]。a=1:栈顶2 > 1,弹出2;栈顶1 == 1,不满足stack.back() < a,不操作,栈仍为[1]。a=2:栈顶1 < 2,res=3,栈变为[1,2]。a=1:栈顶2 > 1,弹出2;栈顶1 == 1,不操作,栈仍为[1]。a=2:栈顶1 < 2,res=4,栈变为[1,2]。
最终res=4,与预期结果一致。
总结
这段代码通过单调递增栈巧妙地维护了 “最小非负整数的递增序列”,确保每次操作都针对 “当前未被覆盖的最小非 0 元素”,从而得到最少操作次数,时间复杂度为O(n)(每个元素最多入栈和出栈一次)。
