【LeetCode】7.栈与队列

总目录:

LeetCode系列导航目录

 

0.理论基础

0.1.要点

栈:先进后出

队列:先进先出

双端队列:两端都可以增删

优先队列:大根堆,压入的数据自动排序,较大值在前;小根堆反之

 

1.用栈实现队列

1.1.问题描述

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
    void push(int x) 将元素 x 推到队列的末尾
    int pop() 从队列的开头移除并返回元素
    int peek() 返回队列开头的元素
    boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
    你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
链接:https://leetcode.cn/problems/implement-queue-using-stacks

1.2.要点

1模拟法,用1个栈存顺序的数据,另一个栈用作缓存

1.3.代码实例

模拟法

 1 class MyQueue {
 2 private:
 3     bool inCatch=false;
 4     stack<int> s1,s2;
 5 public:
 6     MyQueue() {
 7         while(!s1.empty()){
 8             s1.pop();
 9         }
10         while(!s2.empty()){
11             s2.pop();
12         }
13     }
14     
15     void push(int x) {
16         if(inCatch){
17             while(!s2.empty()){
18                 s1.push(s2.top());
19                 s2.pop();
20             }
21             inCatch=false;
22         }
23         s1.push(x);
24     }
25     
26     int pop() {
27         int ret=0;
28         if(!inCatch){
29             while(!s1.empty()){
30                 s2.push(s1.top());
31                 s1.pop();
32             }
33             inCatch=true;
34         }
35 
36         if(s2.empty()){
37             return ret;
38         }
39         ret=s2.top();
40         s2.pop();
41         return ret;
42     }
43     
44     int peek() {
45         int ret=0;
46         if(!inCatch){
47             while(!s1.empty()){
48                 s2.push(s1.top());
49                 s1.pop();
50             }
51             inCatch=true;
52         }
53 
54         if(s2.empty()){
55             return ret;
56         }
57         ret=s2.top();
58         return ret;
59     }
60     
61     bool empty() {
62         return s1.empty()&&s2.empty();
63     }
64 };
65 
66 /**
67  * Your MyQueue object will be instantiated and called as such:
68  * MyQueue* obj = new MyQueue();
69  * obj->push(x);
70  * int param_2 = obj->pop();
71  * int param_3 = obj->peek();
72  * bool param_4 = obj->empty();
73  */
View Code

 

2.用队列实现栈

2.1.问题描述

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
    void push(int x) 将元素 x 压入栈顶。
    int pop() 移除并返回栈顶元素。
    int top() 返回栈顶元素。
    boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
    你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
链接:https://leetcode.cn/problems/implement-stack-using-queues

2.2.要点

1模拟法,用1个队列存数据,另外一个队列作缓存

2.3.代码实例

模拟法

 1 class MyStack {
 2 private:
 3     bool inCatch=false;
 4     queue<int> q1,q2;
 5 public:
 6     MyStack() {
 7         while(!q1.empty()){
 8             q1.pop();
 9         }
10         while(!q2.empty()){
11             q2.pop();
12         }
13     }
14     
15     void push(int x) {
16         if(inCatch){
17             while(!q2.empty()){
18                 q1.push(q2.front());
19                 q2.pop();
20             }
21             inCatch=false;
22         }
23         q1.push(x);
24     }
25     
26     int pop() {
27         int ret=0;
28         if(inCatch){
29             if(q2.empty()){
30                 return ret;
31             }
32             while(q2.size()>1){
33                 q1.push(q2.front());
34                 q2.pop();
35             }
36             ret=q2.front();
37             q2.pop();
38             inCatch=false;
39         }
40         else{
41             if(q1.empty()){
42                 return ret;
43             }
44             while(q1.size()>1){
45                 q2.push(q1.front());
46                 q1.pop();
47             }
48             ret=q1.front();
49             q1.pop();
50             inCatch=true;
51         }
52 
53         return ret;
54     }
55     
56     int top() {
57         int ret=0;
58         if(inCatch){
59             if(q2.empty()){
60                 return ret;
61             }
62             while(q2.size()>1){
63                 q1.push(q2.front());
64                 q2.pop();
65             }
66             ret=q2.front();
67             q1.push(q2.front());
68             q2.pop();
69             inCatch=false;
70         }
71         else{
72             if(q1.empty()){
73                 return ret;
74             }
75             while(q1.size()>1){
76                 q2.push(q1.front());
77                 q1.pop();
78             }
79             ret=q1.front();
80             q2.push(q1.front());
81             q1.pop();
82             inCatch=true;
83         }
84 
85         return ret;
86     }
87     
88     bool empty() {
89         return q1.empty()&&q2.empty();
90     }
91 };
View Code

 

3.有效的括号

3.1.问题描述

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
    左括号必须用相同类型的右括号闭合。
    左括号必须以正确的顺序闭合。
    每个右括号都有一个对应的相同类型的左括号。
链接:https://leetcode.cn/problems/valid-parentheses

3.2.要点

当遇到闭合符号时,前一个符号必须是与其对应的左侧起始符号。

1辅助栈

检查字符串长度是否是奇数,奇数则直接返回false

建立右侧符号与其左侧符号的key-value集合,遍历字符串,当遇到非右侧符号时直接压栈,当遇到右侧符号时检查栈顶是否为其对应的左侧符号、不是则返回false

最后检查栈是否为空,否则为false

3.3.代码实例

辅助栈

 1 class Solution {
 2 public:
 3     bool isValid(string s) {
 4         int n = s.size();
 5         if (n % 2 == 1) {
 6             return false;
 7         }
 8 
 9         unordered_map<char, char> pairs = {
10             {')', '('},
11             {']', '['},
12             {'}', '{'}
13         };
14         stack<char> stk;
15         for (char ch: s) {
16             if (pairs.count(ch)) {
17                 if (stk.empty() || stk.top() != pairs[ch]) {
18                     return false;
19                 }
20                 stk.pop();
21             }
22             else {
23                 stk.push(ch);
24             }
25         }
26         return stk.empty();
27     }
28 };
View Code

 

4.删除字符串中所有相邻重复项

4.1.问题描述

给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

链接:https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/

4.2.要点

1栈

压入数据时检查栈顶元素是否相同,相同则弹栈,不同则入栈,最后拼接字符串

4.3.代码实例

由于 std::stringstd::string 类本身就提供了类似「入栈」和「出栈」的接口,因此我们直接将需要被返回的字符串作为栈即可

 

 1 class Solution {
 2 public:
 3     string removeDuplicates(string s) {
 4         string stk;
 5         for (char ch : s) {
 6             if (!stk.empty() && stk.back() == ch) {
 7                 stk.pop_back();
 8             } else {
 9                 stk.push_back(ch);
10             }
11         }
12         return stk;
13     }
14 };
View Code

 

辅助栈

 1 class Solution {
 2 public:
 3     string removeDuplicates(string s) {
 4         stack<char> st;
 5         for(int i=0;i<s.length();i++){
 6             if(!st.empty()&&st.top()==s[i]){
 7                 st.pop();
 8                 continue;
 9             }
10 
11             st.push(s[i]);
12         }
13         int nums=0;
14         stack<char> catchS;
15         while(!st.empty()){
16             catchS.push(st.top());
17             st.pop();
18             nums++;
19         }
20 
21         string strRet;
22         for(int i=0;i<nums;i++){
23             strRet+=catchS.top();
24             catchS.pop();
25         }
26         return strRet;
27     }
28 };
View Code

 

5.逆波兰表达式求值

5.1.问题描述

根据 逆波兰表示法,求表达式的值。
有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
注意 两个整数之间的除法只保留整数部分。
可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
链接:https://leetcode.cn/problems/evaluate-reverse-polish-notation

5.2.要点

适合栈的特点,根据后面遇到的元素选择策略来处理前面相邻元素。

遇到运算符则将前两个数字拿出来作运算,并将结果压回栈里。

5.3.代码实例

 1 class Solution {
 2 public:
 3     int evalRPN(vector<string>& tokens) {
 4         stack<long long> stk;
 5         int n = tokens.size();
 6         for (int i = 0; i < n; i++) {
 7             string& token = tokens[i];
 8             if (isNumber(token)) {
 9                 stk.push((long long)atoi(token.c_str()));
10             } else {
11                 long long num2 = stk.top();
12                 stk.pop();
13                 long long num1 = stk.top();
14                 stk.pop();
15                 switch (token[0]) {
16                     case '+':
17                         stk.push(num1 + num2);
18                         break;
19                     case '-':
20                         stk.push(num1 - num2);
21                         break;
22                     case '*':
23                         stk.push(num1 * num2);
24                         break;
25                     case '/':
26                         stk.push(num1 / num2);
27                         break;
28                 }
29             }
30         }
31         return (int)stk.top();
32     }
33 
34     bool isNumber(string& token) {
35         return !(token == "+" || token == "-" || token == "*" || token == "/");
36     }
37 };
View Code

 

6.问滑动窗口最大值

6.1.问题描述

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

链接:https://leetcode.cn/problems/sliding-window-maximum/

6.2.要点

1大根堆,如何处理还在堆中、滑出了窗口的元素?

2单调队列

3分块+预处理

这题得好好学习一下了。

法一中,去除最大值在窗口左边的方法要学;

法二中,虽然知道类似的单调栈这种东西,但是实际应用要学,这题可以作为一个例子;

法三的分块的做法也曾经见过一次,但是没注意,以为是偏方,现在又出现了,看来也得学

6.3.代码实例

大根堆

 1 class Solution {
 2 public:
 3     vector<int> maxSlidingWindow(vector<int>& nums, int k) {
 4         int n = nums.size();
 5         priority_queue<pair<int, int>> q;
 6         for (int i = 0; i < k; ++i) {
 7             q.emplace(nums[i], i);
 8         }
 9         vector<int> ans = {q.top().first};
10         for (int i = k; i < n; ++i) {
11             //入堆
12             q.emplace(nums[i], i);
13             
14             //清除失效元素
15             while (q.top().second <= i - k) {
16                 q.pop();
17             }
18 
19             //取最大值
20             ans.push_back(q.top().first);
21         }
22         return ans;
23     }
24 };
View Code

单调队列链接:https://leetcode.cn/problems/sliding-window-maximum/solution/hua-dong-chuang-kou-zui-da-zhi-by-leetco-ki6m/

单调队列小说:

单调队列真是一种让人感到五味杂陈的数据结构,它的维护过程更是如此.....就拿此题来说,队头最大,往队尾方向单调......有机会站在队头的老大永远心狠手辣,当它从队尾杀进去的时候,如果它发现这里面没一个够自己打的,它会毫无人性地屠城,把原先队里的人头全部丢出去,转身建立起自己的政权,野心勃勃地准备开创一个新的王朝.....这时候,它的人格竟发生了一百八十度大反转,它变成了一位胸怀宽广的慈父!它热情地请那些新来的“小个子”们入住自己的王国......然而,这些小个子似乎天性都是一样的——嫉妒心强,倘若见到比自己还小的居然更早入住王国,它们会心狠手辣地找一个夜晚把它们通通干掉,好让自己享受更大的“蛋糕”;当然,遇到比自己强大的,它们也没辙,乖乖夹起尾巴做人。像这样的暗杀事件每天都在上演,虽然王国里日益笼罩上白色恐怖,但是好在没有后来者强大到足以干翻国王,江山还算能稳住。直到有一天,闯进来了一位真正厉害的角色,就像当年打江山的国王一样,手段狠辣,野心膨胀,于是又是大屠城......历史总是轮回的。

有那么一位国王足够强大,经历过层层血腥屠杀之后坐上了王座、镇压了次次颠覆行动,但他已经坐了足够久,久到时间的洪流滚滚向前把他抛弃而自然死去,帝国平平静静地迎来了第一顺位的新王登基,历史翻开了新的一页、添上新的一笔。

 1 class Solution {
 2 public:
 3     vector<int> maxSlidingWindow(vector<int>& nums, int k) {
 4         int n = nums.size();
 5         deque<int> q;
 6         for (int i = 0; i < k; ++i) {
 7             while (!q.empty() && nums[i] >= nums[q.back()]) {
 8                 q.pop_back();
 9             }
10             q.push_back(i);
11         }
12 
13         vector<int> ans = {nums[q.front()]};
14         for (int i = k; i < n; ++i) {
15             while (!q.empty() && nums[i] >= nums[q.back()]) {
16                 q.pop_back();
17             }
18             q.push_back(i);
19             while (q.front() <= i - k) {
20                 q.pop_front();
21             }
22             ans.push_back(nums[q.front()]);
23         }
24         return ans;
25     }
26 };
View Code

分块+预处理,https://leetcode.cn/problems/sliding-window-maximum/solution/hua-dong-chuang-kou-zui-da-zhi-by-leetco-ki6m/

 1 class Solution {
 2 public:
 3     vector<int> maxSlidingWindow(vector<int>& nums, int k) {
 4         int n = nums.size();
 5         vector<int> prefixMax(n), suffixMax(n);
 6         for (int i = 0; i < n; ++i) {
 7             if (i % k == 0) {
 8                 prefixMax[i] = nums[i];
 9             }
10             else {
11                 prefixMax[i] = max(prefixMax[i - 1], nums[i]);
12             }
13         }
14         for (int i = n - 1; i >= 0; --i) {
15             if (i == n - 1 || (i + 1) % k == 0) {
16                 suffixMax[i] = nums[i];
17             }
18             else {
19                 suffixMax[i] = max(suffixMax[i + 1], nums[i]);
20             }
21         }
22 
23         vector<int> ans;
24         for (int i = 0; i <= n - k; ++i) {
25             ans.push_back(max(suffixMax[i], prefixMax[i + k - 1]));
26         }
27         return ans;
28     }
29 };
View Code

 

7.前K个高频元素

7.1.问题描述

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2;输出: [1,2]
链接:https://leetcode.cn/problems/top-k-frequent-elements

7.2.要点

1哈希+大根堆

先用map统计出现次数,再用大根堆排序

2哈希+小根堆

先用map统计出现次数,再用小根堆,这里的优化之处在于,先将小根堆塞满k个,然后小于堆顶的元素不必再入堆,大于等于堆顶的元素在入堆后要弹堆,始终保持堆中只有k个元素,减轻堆排序计算量。

另外,注意学习小根堆比较的重载。

7.3.代码实例

哈希+大根堆

 1 class Solution {
 2 public:
 3     vector<int> topKFrequent(vector<int>& nums, int k) {
 4         int dataLen=nums.size();
 5         map<int,int> numsMap;
 6         for(int i=0;i<dataLen;i++){
 7             numsMap[nums[i]]++;
 8         }
 9 
10         priority_queue<pair<int, int>> q;
11         for(auto& p:numsMap){
12             q.emplace(p.second,p.first);
13         }
14 
15         vector<int> ret;
16         int cnt=0;
17         while(cnt<k && !q.empty()){
18             ret.push_back(q.top().second);
19             q.pop();
20             cnt++;
21         }
22 
23         return ret;
24     }
25 };
View Code

哈希+小根堆

 1 class Solution {
 2 public:
 3     // 小顶堆
 4     class mycomparison {
 5         public:
 6             bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
 7                 return lhs.second > rhs.second;
 8             }
 9     };
10     vector<int> topKFrequent(vector<int>& nums, int k) {
11         // 要统计元素出现频率
12         unordered_map<int, int> map; // map<num[i], 对应出现次数>
13         for (int i = 0; i < nums.size(); i++) {
14             map[nums[i]]++;
15         }
16 
17         // 对频率排序
18         // 定义一个小顶堆,大小为k
19         priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
20 
21         // 用固定大小为k的小顶堆,扫描所有频率的数值
22         for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
23             pri_que.push(*it);
24             if (pri_que.size() > k) {// 如果堆的大小大于k,则队列弹出,保证堆的大小一直为k
25                 pri_que.pop();
26             }
27         }
28 
29         // 找出前k个高频元素,因为小顶堆先弹出的是最小的,所以倒序输出到数组
30         vector<int> ans(k);
31         for (int i = k - 1; i >= 0; i--) {
32             ans[i] = pri_que.top().first;
33             pri_que.pop();
34         }
35         return ans;
36     }
37 };
View Code

 

8.总结

总结参考资料

8.1.栈和队列的底层问题

(1)C++中stack 是容器么?

不是,栈和队列被归类为container adapter(容器适配器)。

(2)我们使用的STL中stack是如何实现的?

默认以deque作为底层容器。

(3)stack 提供迭代器来遍历stack空间么?

栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator),队列同理。

不像是set 或者map 提供迭代器iterator来遍历所有元素。

8.2.栈的经典问题

(1)递归的实现

(2)括号匹配问题

(3)字符串相邻字符去重问题

(4)逆波兰表达式计算问题

8.3.队列的经典问题

(1)滑动窗口最大值,使用大根堆、单调队列等等

(2)求前K个高频元素

(3)在树中层序遍历、广度搜索

 

 

 

 

xxx.问题

xxx.1.问题描述

111

xxx.2.要点

222

xxx.3.代码实例

333

posted @ 2022-12-15 21:25  啊原来是这样呀  阅读(22)  评论(0编辑  收藏  举报