栈和队列
栈和队列
232.用栈实现队列
双栈的想法。需要在出栈时转移栈中元素到另一个栈,入栈时转移栈中元素到初始的栈。
class MyQueue { public: stack<int> instack,outstack; MyQueue() { } void push(int x) { while(!outstack.empty()) { int element = outstack.top(); outstack.pop(); instack.push(element); } instack.push(x); } int pop() { while(!instack.empty()) { int element = instack.top(); instack.pop(); outstack.push(element); } int ans = outstack.top(); outstack.pop(); return ans; } int peek() { while(!instack.empty()) { int element = instack.top(); instack.pop(); outstack.push(element); } int ans = outstack.top(); return ans; } bool empty() { return instack.empty() && outstack.empty(); } }; /** * Your MyQueue object will be instantiated and called as such: * MyQueue* obj = new MyQueue(); * obj->push(x); * int param_2 = obj->pop(); * int param_3 = obj->peek(); * bool param_4 = obj->empty(); */
225.用队列实现栈
同双栈法。
class MyStack { public: queue<int> a; queue<int> b; MyStack() { } void push(int x) { a.push(x); } int pop() { while(a.size()>1) { b.push(a.front()); a.pop(); } int ans=a.back(); a.pop(); while(!b.empty()) { a.push(b.front()); b.pop(); } return ans; } int top() { int ans=a.back(); while(!a.empty()) { b.push(a.front()); a.pop(); } while(!b.empty()) { a.push(b.front()); b.pop(); } return ans; } bool empty() { return a.empty(); } }; /** * Your MyStack object will be instantiated and called as such: * MyStack* obj = new MyStack(); * obj->push(x); * int param_2 = obj->pop(); * int param_3 = obj->top(); * bool param_4 = obj->empty(); */
1441.用栈操作构建数组
一个个对比需要的数字就行。
class Solution { public: vector<string> buildArray(vector<int>& target, int n) { int counter = 0; vector<string> operations; for (int i = 1; i <= n; i++) { // Match the elements; if(counter >= target.size()) break; operations.emplace_back("Push"); if (i == target[counter]) { counter ++; continue; } else { operations.emplace_back("Pop"); } } return operations; } };
单调栈
栈内部单调递增或单调递减的栈。可以用来记录一些关于可覆盖面积,最近的符合某些条件的下标信息等等。
例如我们有:
[6,7,5,2,4,5,9,3]
从左到右寻找左边第一个比自己小的元素,则可以使用单调栈。
所以:
[-1,0,-1,-1,3,4,5,3]
从右向左寻找右边第一个比自己小的元素,则有:
[2,2,3,8,7,7,7,8]
例如:我们有这样一道题
84. 柱状图中最大的矩形
这时候我们可以利用单调栈来寻找一根柱子能够覆盖的最大范围。
从左向右遍历,当新的元素小于等于栈顶元素时不断出栈,直到栈空或栈顶元素小于当前元素为止。将索引入栈。
class Solution { public: int largestRectangleArea(vector<int>& heights) { int n = heights.size(); vector<int> left(n), right(n); // Initialize the vector; stack<int> mono_stack; for (int i=0;i<n;i++) { // non-empty and the top of the mono-stack is bigger than heights: while(!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) { mono_stack.pop(); } // When the top is smaller or none in mono_stack, put the values. left[i] = (mono_stack.empty() ? -1 : mono_stack.top()); mono_stack.push(i); } mono_stack = stack<int>(); for (int i=n-1;i > -1 ;i--) { // non-empty and the top of the mono-stack is bigger than heights: while(!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) { mono_stack.pop(); } // When the top is smaller or none in mono_stack, put the values. right[i] = (mono_stack.empty() ? n : mono_stack.top()); mono_stack.push(i); } int ans = 0; for (int i = 0; i < n; i++) { ans = max(ans, heights[i] * (right[i]-left[i]-1)); } return ans; } };
84. 柱状图中最大的矩形
题解一:单调栈
class Solution { public: int largestRectangleArea(vector<int>& heights) { int n = heights.size(); vector<int> left(n), right(n); // Initialize the vector; stack<int> mono_stack; for (int i=0;i<n;i++) { // non-empty and the top of the mono-stack is bigger than heights: while(!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) { mono_stack.pop(); } // When the top is smaller or none in mono_stack, put the values. left[i] = (mono_stack.empty() ? -1 : mono_stack.top()); mono_stack.push(i); } mono_stack = stack<int>(); for (int i=n-1;i > -1 ;i--) { // non-empty and the top of the mono-stack is bigger than heights: while(!mono_stack.empty() && heights[mono_stack.top()] >= heights[i]) { mono_stack.pop(); } // When the top is smaller or none in mono_stack, put the values. right[i] = (mono_stack.empty() ? n : mono_stack.top()); mono_stack.push(i); } int ans = 0; for (int i = 0; i < n; i++) { ans = max(ans, heights[i] * (right[i]-left[i]-1)); } return ans; } };
优化
class Solution { public: int largestRectangleArea(vector<int>& heights) { int n = heights.size(),ans=0; stack<int> mono_stack; mono_stack.push(-1); for (int i=0;i<n;i++) { while(mono_stack.top()!=-1 && heights[i] < heights[mono_stack.top()]) { int cur_height = heights[mono_stack.top()]; mono_stack.pop(); ans = max(ans, cur_height*(i - mono_stack.top() - 1)); } mono_stack.push(i); } while(mono_stack.top()!=-1) { int cur_height = heights[mono_stack.top()]; mono_stack.pop(); ans = max(ans, cur_height*((n - 1) - mono_stack.top())); } return ans; } };
42. 接雨水
也是经典的单调栈问题。
方法一:传统单调栈
例如我们看题目中的实例1.
第一步:从左向右寻找左边第一个比自己小的元素,我们有:
height=[0,1,0,2,1,0,1,3,2,1,2,1]
index=[-1,0,-1,2,2,-1,5,6,6,5,9,9]
这里我们需要计算的是凹陷的面积。这样,我们就需要对原来的单调栈进行稍微的修改。要寻找到该元素左侧第一个比我们大的元素和右侧第一个比我们大或相等的元素。需要注意两边有且仅有一边含有等于。两边都有等于会导致重复计数,两边都没有等于会导致缺少。
-
从左往右看,寻找左边第一个比自己大的数。
- 当当前的数字比栈顶元素指向的数小时,出栈,直到栈顶元素指向的数比当前要小或者栈为空,将栈顶元素或者-1记录到数组中。
- 当当前元素比栈顶元素指向的数大时,直接将栈顶元素记录到数组中。
- 将当前元素下标入栈。
-
从右向左看,寻找右边第一个比自己大于大于或等于的数。
- 当当前的数字比栈顶元素指向的数大时,出栈,直到栈顶元素指向的数比当前元素要大或者栈为空,将栈顶元素或者n记录到数组中。
- 当当前元素比栈顶元素指向的数小时,直接将栈顶元素记录到数组中。
- 将当前元素下标入栈。
-
计算凹陷面积,也就是
这样我们就得到了我们需要的面积。
class Solution { public: int trap(vector<int>& height) { // 单调栈,两次遍历。 int n = height.size(); vector<int> left(n),right(n); stack<int> mono_stack; for(int i = 0; i < n; i++) { // 自左向右寻找,第一个比该元素左边更大的元素。 while(!mono_stack.empty() && height[mono_stack.top()] <= height[i]) { mono_stack.pop(); } left[i] = mono_stack.empty() ? -1 : mono_stack.top(); mono_stack.push(i); } mono_stack=stack<int>(); for(int i = n-1; i >= 0; i--) { // 自右向左寻找,第一个比该元素大于等于的右边元素。 while(!mono_stack.empty() && height[mono_stack.top()] < height[i]) { mono_stack.pop(); } right[i] = mono_stack.empty() ? n : mono_stack.top(); mono_stack.push(i); } int area = 0; for(int i = 1; i < n-1; i++) { if (left[i] == -1 || right[i] == n) continue; area += (right[i] - left[i] - 1) * (min(height[left[i]], height[right[i]]) - height[i]); } return area; } };
739. 每日温度
class Solution { public: vector<int> dailyTemperatures(vector<int>& temperatures) { vector<int> index(temperatures.size()); stack<int> m; for(int i = temperatures.size()-1; i >= 0; i--) { while(!m.empty() && temperatures[m.top()] <= temperatures[i]) m.pop(); index[i] = m.empty() ? 0 : m.top() - i; m.push(i); } return index; } };
去除重复字符
单调栈+贪心
-
去除一个字符使得字符串的字典序最小:寻找到字符串中最早的逆序对,并去除前一个。
-
也就是寻找到
。 -
朴素解法:多次扫描字符串并且按照上述方法删除。
- 造成大量中间字符串!!
-
采用单调栈维护去除字符后得到的字符串。
-
单调栈自栈顶到栈底递减。并且为了保证每个字符出现且仅出现一次:
- 记录每个字符是否出现在栈中,如果出现,则不能加入字符。
- 弹出栈顶字符时,不能使得其计数为0.
class Solution { public: string removeDuplicateLetters(string s) { string stack; vector<int> calc(26,0); vector<int> inStack(26,0); // 计算出现次数。 for(auto i:s) calc[i-'a']++; for(auto i:s) { // 没有入过栈则入栈 if(!inStack[i-'a']) { // 不断弹栈直到栈顶元素小于入栈元素。 while(!stack.empty() && stack.back() > i) { // 不能将仅出现过一次的字符弹出。一旦遇到则停止。 if(calc[stack.back() - 'a']) { inStack[stack.back()-'a'] = 0; stack.pop_back(); } else break; } inStack[i-'a'] = 1; stack.push_back(i); } calc[i-'a']--; } return stack; } };
方法一:模拟栈
class Solution { public: int longestValidParentheses(string s) { stack<int> index; int ans = 0; for(int i = 0; i < s.length(); i++) { if(index.empty() || s[i] == s[index.top()] || (s[i] == '(' && s[index.top()]==')')) index.push(i); else if(s[i] == ')' && s[index.top()] == '(') { index.pop(); if(!index.empty()) ans = max(ans, i - index.top()); else ans = max(ans, i+1); } else { index.push(i); } } return ans; } };
方法二:动态规划
我们设dp[i]为以dp[i]的字符作为结尾的子字符串的个数。当我们有:...()
时,我们很容易知道:
dp[i] = dp[i-2] + 2
.
当出现嵌套括号时,我们有:...))
。这时候我们需要找到匹配的第一个括号,也就是...(...))
来得到我们的结果。这时候我们上一个就记录了在嵌套括号中匹配的子字符串的长度,当该子字符串前面一个字符为(
时,则匹配成功。
...([已经匹配的子字符串长度])))
,找到两个右括号,寻找已经匹配的子字符串前是否有括号,有则形成更大的子字符串,并且将它形成的子字符串个数也进行相加。
dp[i] = dp[i-1] + dp[i-2-dp[i-1]] if s[i-1-dp[i-1]] == '('
class Solution { public: int longestValidParentheses(string s) { vector<int> dp(s.length(), 0); // 1. s[i-1] = '(', s[i] = ')' // dp[i] = dp[i-2] + 2; // 2. s[i] = ')', s[i-1]=')',回溯到上一个‘(’。 // dp[i] = dp[i-1] + dp[i - 2 - dp[i-1]] + 2 // ((())) 0 0 0 2(dp[2] + dp[1] + 2) 4(dp[3]=2 + dp[0] + 2) if(!s.length()) return 0; for(int i = 0; i < s.length(); i++) { if(i < 1) continue; if(s[i-1] == '(' && s[i] == ')') { if(i < 2) dp[i] = 2; else dp[i] = dp[i-2] + 2; } else if(s[i-1]==')' && s[i] == ')') { if(i-1-dp[i-1] >= 0 && s[i-1-dp[i-1]] == '(') { if(i-2-dp[i-1] >= 0) dp[i] = dp[i-1] + dp[i-2-dp[i-1]] + 2; else dp[i] = dp[i-1]+2; } } } return *max_element(dp.begin(),dp.end()); } };
方法一:pair优先队列
class Solution { public: // Method 1: using priority queue. vector<int> maxSlidingWindow(vector<int> &nums, int k) { auto cmp = [](pair<int,int> a, pair<int,int> b) -> bool { return a.first < b.first; }; priority_queue<pair<int,int>, vector<pair<int,int> >, decltype(cmp)> pq(cmp); for(int i = 0; i < k; i++) pq.emplace(make_pair(nums[i],i)); vector<int> ans; ans.push_back(pq.top().first); for(int i = k; i < nums.size(); i++) { pq.emplace(make_pair(nums[i], i)); while(pq.top().second <= i-k) pq.pop(); ans.push_back(pq.top().first); } return ans; } };
方法二:单调队列
我们的单调队列需要满足两个情况:
- 最左边的元素应该始终在滑动窗口内;
- 单调队列内部单调递减。
也就是说,我们的单调队列中应该是最大的元素位于队的一端,最小的元素位于队的另一端并且在合适的下标内。这样我们可以用双端队列来实现。
因此,新元素从队尾不断比较指导符合递减规律,同时超过滑动窗口区域的元素离开即可。
单调递减队列:nums = [1,3,1,2,0,5], k = 3
{1}
{3}
{3,1} front = 3
{3,2} front = 3
{2,0} front = 2
{5} front = 5
有例如: nums = [-7,-8,7,5,7,1,6,0], k = 4
{-7}
{-7,-8}
{7}
{7,5} front = 7
{7,7} front = 7
{7,7,1} front = 7
{7,6} back 7 离开了窗口区域。front = 7
{7,6,0} front = 7
如果要寻找最小值,则需要单调递增队列。
class Solution { public: // Method 2: Mono-queue. vector<int> maxSlidingWindow(vector<int> & nums, int k) { deque<int> dq; for(int i = 0; i < k; i++) { while(!dq.empty() && dq.back()<nums[i]) dq.pop_back(); dq.push_back(nums[i]); } vector<int> ans; ans.push_back(dq.front()); // 1,3,1,2,0,5 -> {3,1} for(int i = k; i < nums.size(); i++) { while(!dq.empty() && dq.back() < nums[i]) dq.pop_back(); if(!dq.empty() && dq.front() == nums[i-k]) dq.pop_front(); dq.push_back(nums[i]); ans.push_back(dq.front()); } return ans; } };
- 单调栈/单调队列
- 寻找最大矩形覆盖面积,雨水(单调栈)
- 最大值最小化/最小值最大化(二分+单调双端队列)
- 优先队列的使用,大根堆(less < ),小根堆(greater > ).
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了