本篇文章的读者是初学者,旨在帮助他们了解单调栈,同时也检验自己的水平,从而共同提高代码水平和效率,共勉之。
本文为读者详细介绍了单调栈和循环单调栈算法。
单调栈
单调栈(Monotonic Stack)是一种数据结构,通常用于解决一些与单调性相关的问题,例如找到数组中每个元素的下一个更大元素或下一个更小元素等。这种栈的特点是栈内元素保持单调性,可以是递增或递减的。
在单调栈中,元素入栈时,会影响栈内元素的单调性,不满足单调性的元素将会被弹出。这样,栈内的元素就能反映一些与单调性相关的信息。
下面是单调栈的一些基本应用:
- 寻找下一个更大元素:
- 给定一个数组,对于每个元素,找到数组中下一个比它更大的元素。这个问题可以使用单调递减栈来解决。
- 寻找下一个更小元素:
- 类似于上述问题,只不过这次是找下一个更小的元素。这可以使用单调递增栈来解决。
- 求解柱状图中的最大矩形面积:
- 给定一个柱状图,找到其中最大的矩形面积。这可以通过单调递增栈来实现。
- 窗口内的最大值和最小值:
- 在一个滑动窗口中,找到窗口内的最大值或最小值。这可以通过维护一个单调递增或递减的栈来实现。
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
vector<int> nextGreaterElement(const vector<int>& nums) {
int n = nums.size();
vector<int> result(n, -1);
stack<int> s;
for (int i = 0; i < n; ++i) {
while (!s.empty() && nums[i] > nums[s.top()]) {
result[s.top()] = nums[i];
s.pop();
}
s.push(i);
}
return result;
}
int main() {
vector<int> nums = {4, 2, 10, 1, 5, 6};
vector<int> result = nextGreaterElement(nums);
cout << "Next greater elements: ";
for (int num : result) {
cout << num << " ";
}
return 0;
}
-
初始状态: 数组为
{4, 2, 10, 1, 5, 6}
,栈为空,结果数组初始化为{ -1, -1, -1, -1, -1, -1 }
。 -
处理元素 4:
- 元素 4 入栈,栈变为
{ 0 }
。
- 元素 4 入栈,栈变为
-
处理元素 2:
- 元素 2 比栈顶元素 4 小,入栈,栈变为
{ 0, 1 }
。
- 元素 2 比栈顶元素 4 小,入栈,栈变为
-
处理元素 10:
- 元素 10 大于栈顶元素 2 和 4,所以更新栈顶元素对应的结果数组值,并继续弹出栈顶元素,直到栈为空或者栈顶元素大于等于 10。结果数组变为
{ 10, 10, -1, -1, -1, -1 }
,栈变为{ 2 }
。
- 元素 10 大于栈顶元素 2 和 4,所以更新栈顶元素对应的结果数组值,并继续弹出栈顶元素,直到栈为空或者栈顶元素大于等于 10。结果数组变为
-
处理元素 1:
- 元素 1 比栈顶元素 10 小,入栈,栈变为
{ 2, 3 }
。
- 元素 1 比栈顶元素 10 小,入栈,栈变为
-
处理元素 5:
- 元素 5 大于栈顶元素 1,所以更新栈顶元素对应的结果数组值,并继续弹出栈顶元素,直到栈为空或者栈顶元素大于等于 5。结果数组变为
{ 10, 10, -1, 5, -1, -1 }
,栈变为{ 2, 4 }
。
- 元素 5 大于栈顶元素 1,所以更新栈顶元素对应的结果数组值,并继续弹出栈顶元素,直到栈为空或者栈顶元素大于等于 5。结果数组变为
-
处理元素 6:
- 元素 6 大于栈顶元素 5,所以更新栈顶元素对应的结果数组值,并继续弹出栈顶元素,直到栈为空或者栈顶元素大于等于 6。结果数组变为
{ 10, 10, -1, 5, 6, -1 }
,栈变为{ 2, 5 }
。
- 元素 6 大于栈顶元素 5,所以更新栈顶元素对应的结果数组值,并继续弹出栈顶元素,直到栈为空或者栈顶元素大于等于 6。结果数组变为
-
最终结果:
- 栈中剩余的元素 5 没有下一个更大元素,所以更新对应的结果数组值为 -1。最终的结果数组为
{ 10, 10, -1, 5, 6, -1 }
循环单调栈
循环单调栈(Circular Monotonic Stack)是单调栈的一种变体,专用于解决循环数组相关的问题。循环数组是指数组的末尾与开头相连,形成一个环状结构。在处理循环数组时,我们通常需要考虑元素的相对位置,而循环单调栈正是用来应对这类情况的。
循环单调栈的基本思想与普通单调栈相似,不同之处在于它处理循环的特殊性。在循环单调栈中,当遍历到数组的最后一个元素时,会再次回到数组的起始位置。因此,在处理循环数组时,我们可以模拟两遍遍历,以处理循环的影响。
- 栈中剩余的元素 5 没有下一个更大元素,所以更新对应的结果数组值为 -1。最终的结果数组为
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
vector<int> nextGreaterElements(const vector<int>& nums) {
int n = nums.size();
vector<int> result(n, -1); // 初始化结果数组,全部设置为 -1
stack<int> s; // 单调递减栈,栈内存储数组元素的索引
// 对循环数组进行两轮遍历
for (int i = 0; i < 2 * n; ++i) {
int num = nums[i % n]; // 获取数组元素,通过取余来实现循环
// 当栈非空且当前元素大于栈顶元素时,表示找到了栈顶元素的下一个更大元素
while (!s.empty() && num > nums[s.top()]) {
result[s.top()] = num; // 将结果数组中对应位置设置为当前元素的值
s.pop(); // 弹出栈顶元素,继续判断
}
s.push(i % n); // 将当前元素的索引入栈(只需要对前 n 个元素入栈)
}
return result;
}
int main() {
vector<int> nums = {4, 2, 10, 1, 5, 3};
vector<int> result = nextGreaterElements(nums);
cout << "Next greater elements: ";
for (int value : result) {
cout << value << " ";
}
return 0;
}