栈(stack)是一种常见的严格限制处理顺序的线性表数据结构,遵循LIFO后进先出原则。
5 个基本操作: push()、top()、pop()、empty()、size()
top:返回栈顶不删除
栈的使用场景有:
函数调用时的上下文管理
深度优先搜索 DFS
难度: 简单
题目表述:
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
代码(C++):
class Solution {
public:
bool isValid(string s) {
stack<char> stk;
unordered_map<char, char> pairs = {{'(', ')'}, {'{', '}'}, {'[', ']'}};
for (int i = 0; i < s.size(); i++) {
if (pairs.count(s[i])) {
stk.push(s[i]);
} else {
if (!stk.empty() && pairs[stk.top()] == s[i]) {
stk.pop();
} else {
return false;
}
}
}
return stk.empty();
}
};
题解: 栈+哈希
难度: 困难
题目表述:
给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
代码(C++):
class Solution {
public:
int calculate(string s) {
stack<int> stk;
stk.push(1);
int sign = 1;
int n = s.length();
int i = 0;
long sum = 0;
while (i < n) {
if (s[i] == ' ') {
i++;
continue;
} else if (s[i] == '+') {
sign = stk.top();
i++;
} else if (s[i] == '-') {
sign = -stk.top();
i++;
} else if (s[i] == '(') {
stk.push(sign);
i++;
} else if (s[i] == ')') {
stk.pop();
i++;
} else {
long num = 0;
while (i < n && s[i] >= '0' && s[i] <= '9') {
num = num * 10 + s[i] - '0';
i++;
}
sum += sign * num;
}
}
return sum;
}
};
题解: 括号展开 + 栈
维护一个栈,栈顶元素记录了括号 ‘(’ 所在位置由前面所有括号「共同形成」的符号
难度: 中等
题目表述:
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
代码(C++):
class MinStack {
stack<int> stk;
stack<int> minStk;
public:
MinStack() {
minStk.push(INT_MAX);
}
void push(int val) {
stk.push(val);
minStk.push(min(minStk.top(),val));
}
void pop() {
stk.pop();
minStk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return minStk.top();
}
};
题解: 元素栈+最小栈
难度: 中等
题目表述:
给定 pushed 和 popped 两个序列,每个序列中的 值都不重复,只有当它们可能是在最初空栈上进行的推入 push 和弹出 pop 操作序列的结果时,返回 true;否则,返回 false 。
代码(C++):
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> stk;
for (int i = 0, j = 0; j < popped.size(); j++) {
stk.push(pushed[j]);
while (!stk.empty() && stk.top() == popped[i]) {
stk.pop();
i++;
}
}
return stk.empty();
}
};
题解: 栈模拟
先压栈再看能不能弹出
难度: 中等
题目表述:
给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
代码(C++):
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> stk;
int n = tokens.size();
for (int i = 0; i < n; i++) {
string& token = tokens[i];
if (isNumber(token)) {
stk.push(atoi(token.c_str()));
} else {
int num2 = stk.top();
stk.pop();
int num1 = stk.top();
stk.pop();
switch (token[0]) {
case '+':
stk.push(num1 + num2);
break;
case '-':
stk.push(num1 - num2);
break;
case '*':
stk.push(num1 * num2);
break;
case '/':
stk.push(num1 / num2);
break;
}
}
}
return stk.top();
}
bool isNumber(string& token) {
return !(token == "+" || token == "-" || token == "*" || token == "/");
}
};
题解:
逆波兰表达式:
1.如果遇到操作数,则将操作数入栈;
2.如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。
运算符在运算数前面的表达式就是波兰表达式(前缀表达式),运算符在运算数后面的表达式就是逆波兰表达式(后缀表达式)。
中缀表达式:( 1 + 2 ) * ( 3 + 4 )
前缀表达式:* + 1 2 + 3 4
后缀表达式:1 2 + 3 4 + *
难度: 简单
题目表述:
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
代码(C++):
class Solution {
public:
string removeDuplicates(string s) {
string ans = "";
for (int i = 0; i < s.size(); i++) {
if (ans.size() > 0 && s[i] == ans.back()) {
ans.pop_back();
} else {
ans.push_back(s[i]);
}
}
return ans;
}
};
题解:
std::string 类本身就提供了类似「入栈」和「出栈」的接口,因此我们直接将需要被返回的字符串作为栈即可。
难度: 中等
题目表述:
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
代码(C++):
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
stack<int> stk;
vector<int> answer(temperatures.size(), 0);
for (int i = 0; i < temperatures.size(); i++) {
while (!stk.empty() && temperatures[stk.top()] < temperatures[i]) {
answer[stk.top()] = i - stk.top();
stk.pop();
}
stk.push(i);
}
return answer;
}
};
题解: 单调(递减)栈
维护一个存储下标的单调栈,从栈底到栈顶的下标对应的温度列表中的温度依次递减
时间复杂度:O(n)
空间复杂度:O(n)
难度: 中等
题目表述:
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
代码(C++):
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> stk;
unordered_map<int, int> mp;
vector<int> res(nums1.size(), -1);
for (int i = 0; i < nums1.size(); i++) {
mp[nums1[i]] = i;
}
for (int i = 0; i < nums2.size(); i++) {
while (!stk.empty() && nums2[stk.top()] < nums2[i]) {
if (mp.count(nums2[stk.top()]))
res[mp[nums2[stk.top()]]] = nums2[i];
stk.pop();
}
stk.push(i);
}
return res;
}
};
题解: 单调(递减)栈
没有重复元素,我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。
难度: 中等
题目表述:
给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
代码(C++):
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
vector<int> res(nums.size(), -1);
stack<int> stk;
for (int i = 0; i < nums.size() * 2; i++) {
int index = i % nums.size();
while (!stk.empty() && nums[stk.top()] < nums[index]) {
res[stk.top()] = nums[index];
stk.pop();
}
if (i < nums.size()) stk.push(i);
}
return res;
}
};
题解: 单调(递减)栈
模拟走两边nums
难度: 困难
题目表述:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
代码(C++):
class Solution {
public:
int trap(vector<int>& height) {
stack<int> stk;
int res = 0;
for (int i = 0; i < height.size(); i++) {
while (!stk.empty() && height[stk.top()] < height[i]) {
int mid = stk.top();
stk.pop();
if (!stk.empty()) {
res += (i - stk.top() - 1) * (min(height[stk.top()], height[i]) - height[mid]);
}
}
stk.push(i);
}
return res;
}
// 双指针
int trap(vector<int>& height) {
int ans = 0;
int left = 0, right = height.size() - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
};
题解: 单调(递减)栈 / 双指针
找每个柱子左右两边第一个大于该柱子高度的柱子
单调(递减)栈 :按行算
双指针:按列算
难度: 困难
题目表述:
给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
代码(C++):
class Solution {
public:
// 1.优化暴力算法
int maximalRectangle(vector<vector<char>>& matrix) {
int m = matrix.size();
if (m == 0) {
return 0;
}
int n = matrix[0].size();
vector<vector<int>> left(m, vector<int>(n, 0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1') {
left[i][j] = (j == 0 ? 0: left[i][j - 1]) + 1;
}
}
}
int max_area = 0;
// 优化暴力算法
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '0') {
continue;
}
int width = left[i][j];
int area = width;
for (int k = i - 1; k >= 0; k--) {
width = min(width, left[k][j]);
area = max(area, (i - k + 1) * width);
}
max_area = max(max_area, area);
}
}
return max_area;
}
// 2.单调栈 找左右第一个更小后再计算
int maximalRectangle(vector<vector<char>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
int ans = 0;
vector<vector<int>> height(m, vector<int>(n, 0));
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == '1') {
if (i == 0) height[i][j] = 1;
else height[i][j] = height[i - 1][j] + 1;
}
}
}
// 单调递增栈 // 对于每一行使用基于柱状图的方法,对于每一列使用也同理
for (int i = 0; i < m; i++) {
stack<int> stk;
vector<int> left(n, -1), right(n, n);
// 找左右第一个更小
for (int j = n - 1; j >= 0; j--) {
while (!stk.empty() && height[i][stk.top()] >= height[i][j]) {
stk.pop();
}
if (!stk.empty()) right[j] = stk.top();
stk.push(j);
}
stk = stack<int>();
for (int j = 0; j < n; j++) {
while (!stk.empty() && height[i][stk.top()] >= height[i][j]) {
stk.pop();
}
if (!stk.empty()) left[j] = stk.top();
stk.push(j);
}
// 或 同时找左右第一个更小
for (int j = 0; j < n; j++) {
while (!stk.empty() && height[i][stk.top()] >= height[i][j]) {
right[stk.top()] = j;
stk.pop();
}
if (!stk.empty()) left[j] = stk.top();
stk.push(j);
}
// 计算结果
for (int j = 0; j < n; j++) {
ans = max(ans, (right[j] - left[j] - 1) * height[i][j]);
}
}
return ans;
}
// 3.单调栈 找到右边第一个更小后,再和栈里左边第一个更小一起计算出stk.top()对应的结果
int maximalRectangle(vector<vector<char>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
int ans = 0;
vector<vector<int>> height(m, vector<int>(n + 2, 0));
for (int i = 0; i < m; i++) {
for (int j = 1; j < n + 1; j++) {
if (matrix[i][j - 1] == '1') {
if (i == 0) height[i][j] = 1;
else height[i][j] = height[i - 1][j] + 1;
}
}
}
for (int i = 0; i < m; i++) {
stack<int> stk;
stk.push(0);
for (int j = 1; j < n + 2; j++) {
while (height[i][stk.top()] > height[i][j]) {
int mid = stk.top();
stk.pop();
ans = max(ans, (j - stk.top() - 1) * height[i][mid]);
}
stk.push(j);
}
}
return ans;
}
};
题解: 优化暴力算法 / 单调(递增)栈
找每个柱子左右两边第一个小于该柱子的柱子
应用场景:括号匹配问题(计算器)、字符串去重问题、逆波兰表达式问题、最大矩形
常用方法:单调栈
单调栈:
在一维数组中找两侧比我小或大的第一个数的场景就是典型的单调栈应用场景。
单调栈思想:如果有新元素不满足递增/递减,就不断地将栈顶元素出栈,直至满足
/* 1. 分别找左右第一个小于nums[i]的值 */
// 1.1 找左侧第一个小于nums[i]的值
for (int i = 0; i < n; i++) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
left[i] = stk.empty() ? -1 : stk.top(); // 注意第一个没有left值时的赋值
stk.push(i);
}
stk = stack<int>();
// 1.2找右侧第一个小于nums[i]的值
for (int i = n - 1; i >= 0; i--) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
stk.pop();
}
right[i] = stk.empty() ? n : stk.top(); // 注意最后一个没有right值时的赋值
stk.push(i);
}
/* 2. 同时找左右第一个小于nums[i]的值 */
vector<int> left(n, -1), right(n, n); // 赋初值,注意第一个和最后一个没有left和right值时的赋值
for (int i = 0; i < m; ++i) {
while (!stk.empty() && nums[stk.top()] >= nums[i]) {
right[stk.top()] = i;
stk.pop();
}
if (!stk.empty()) left[i] = stk.top();
stk.push(i);
}
玩转 LeetCode 高频 100 题
LeetCode 刷题攻略