栈和队列题目汇总
1、剑指 Offer 09. 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
class CQueue { stack<int> stack1,stack2; public: CQueue() { while (!stack1.empty()) { stack1.pop(); } while (!stack2.empty()) { stack2.pop(); } } void appendTail(int value) { stack1.push(value); } int deleteHead() { // 如果第二个栈为空 if (stack2.empty()) { while (!stack1.empty()) {// 数据从栈1存入栈2,这样在栈2栈顶元素为最先加入元素 stack2.push(stack1.top()); stack1.pop(); } } if (stack2.empty()) { return -1; } else {// 删除第一个加入的节点 int deleteItem = stack2.top(); stack2.pop(); return deleteItem; } } };
232. 用栈实现队列 解题思路 我们可以用两个栈(a,b)来模拟实现队列,其中会有一个临时栈(b)负责在压人数据时帮助调换元素的顺序, 使其满足队列先进先出的特点,再把调换过顺序的元素,放回到a栈,当出队列时,就直接从a栈中出已经调好顺序的元素就可以了。 class MyQueue { public: /** Initialize your data structure here. */ MyQueue() { } /** Push element x to the back of queue. */ void push(int x) { stack<int > temp; while (!a.empty()){ temp.push(a.top()); a.pop(); } temp.push(x); while (!temp.empty()){ a.push(temp.top()); temp.pop(); } } /** Removes the element from in front of queue and returns that element. */ int pop() { int temp=a.top(); a.pop(); return temp; } /** Get the front element. */ int peek() { return a.top(); } /** Returns whether the queue is empty. */ bool empty() { return a.empty(); } private: stack<int> a; }; /** * 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(); */
使用队列实现栈的下列操作:
- push(x) -- 元素 x 入栈
- pop() -- 移除栈顶元素
- top() -- 获取栈顶元素
- empty() -- 返回栈是否为空
思路:
队列模拟栈,其实一个队列就够了,那么我们先说一说两个队列来实现栈的思路。
队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。
所以用栈实现队列, 和用队列实现栈的思路还是不一样的,这取决于这两个数据结构的性质。
但是依然还是要用两个队列来模拟栈,只不过没有输入和输出的关系,而是另一个队列完全是用来备份的!
如下面动画所示,用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
class MyStack { public: queue<int> que1; queue<int> que2; // 辅助队列 /** Initialize your data structure here. */ MyStack() { } /** Push element x onto stack. */ void push(int x) { que1.push(x); } /** Removes the element on top of the stack and returns that element. */ int pop() { int size = que1.size(); size--; while (size--) { // 将que1 导入que2,但要留下最后一个元素 que2.push(que1.front()); que1.pop(); } int result = que1.front(); // 留下的最后一个元素就是我们要返回的值 que1.pop(); que1 = que2; // 再将que2赋值给que1 while(!que2.empty()) { // 清空que2 que2.pop(); } return result; } /** Get the top element. */ int top() { return que1.back(); } /** Returns whether the stack is empty. */ bool empty() { return que1.empty(); } };
3、设计循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
示例:
MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1); // 返回 true
circularQueue.enQueue(2); // 返回 true
circularQueue.enQueue(3); // 返回 true
circularQueue.enQueue(4); // 返回 false,队列已满
circularQueue.Rear(); // 返回 3
circularQueue.isFull(); // 返回 true
circularQueue.deQueue(); // 返回 true
circularQueue.enQueue(4); // 返回 true
circularQueue.Rear(); // 返回 4
提示:
所有的值都在 0 至 1000 的范围内;
操作数将在 1 至 1000 的范围内;
请不要使用内置的队列库。
class MyCircularQueue { int* data; int size; int head; int tail; int capicity; public: /** Initialize your data structure here. Set the size of the queue to be k. */ MyCircularQueue(int k) { data = new int[k]; size = k; head = 0; tail = 0; capicity = 0; } /** Insert an element into the circular queue. Return true if the operation is successful. */ bool enQueue(int value) { if (capicity < size) { data[tail++] = value; ++capicity; if (tail == size) { tail = 0; } return true; } return false; } /** Delete an element from the circular queue. Return true if the operation is successful. */ bool deQueue() { if (capicity > 0) { ++head; --capicity; if (head == size) { head = 0; } return true; } return false; } /** Get the front item from the queue. */ int Front() { if (capicity == 0) { return -1; } return data[head]; } /** Get the last item from the queue. */ int Rear() { if (capicity == 0) { return -1; } return tail == 0 ? data[size - 1] : data[tail - 1]; } /** Checks whether the circular queue is empty or not. */ bool isEmpty() { return capicity == 0; } /** Checks whether the circular queue is full or not. */ bool isFull() { return capicity == size; } }; /** * Your MyCircularQueue object will be instantiated and called as such: * MyCircularQueue* obj = new MyCircularQueue(k); * bool param_1 = obj->enQueue(value); * bool param_2 = obj->deQueue(); * int param_3 = obj->Front(); * int param_4 = obj->Rear(); * bool param_5 = obj->isEmpty(); * bool param_6 = obj->isFull(); */
4、设计实现双端队列。
你的实现需要支持以下操作:
MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。
示例:
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1); // 返回 true
circularDeque.insertLast(2); // 返回 true
circularDeque.insertFront(3); // 返回 true
circularDeque.insertFront(4); // 已经满了,返回 false
circularDeque.getRear(); // 返回 2
circularDeque.isFull(); // 返回 true
circularDeque.deleteLast(); // 返回 true
circularDeque.insertFront(4); // 返回 true
circularDeque.getFront(); // 返回 4
提示:
所有值的范围为 [1, 1000]
操作次数的范围为 [1, 1000]
请不要使用内置的双端队列库。
解题思路
注意:
head是头部待出队位置(获取头元素,即直接访问arr[head])
从头插入,head是逆时针旋转(需要先减1)
从头删除,head是顺时针旋转
tail是尾部待入队位置,(获取尾元素, 需要先将tail逆时针旋转1)
从尾插入,tail是顺时针旋转
从尾删除,tail是逆时针旋转
class MyCircularDeque { public: /** Initialize your data structure here. Set the size of the deque to be k. */ MyCircularDeque(int k) { m_head = 0; m_tail = 0; m_size = 0; m_capacity = k; m_arr = new int[k]; } /** Adds an item at the front of Deque. Return true if the operation is successful. */ bool insertFront(int value) { if (isFull()) return false; //head指向当前元素, 前插时,head逆时针旋转 --m_head; if (m_head < 0) { m_head = m_capacity - 1; } m_arr[m_head] = value; ++m_size; return true; } /** Adds an item at the rear of Deque. Return true if the operation is successful. */ bool insertLast(int value) { if (isFull()) return false; m_arr[m_tail++] = value; if (m_tail == m_capacity) { m_tail = 0; } ++m_size; return true; } /** Deletes an item from the front of Deque. Return true if the operation is successful. */ bool deleteFront() { if (isEmpty()) return false; //从头删除 head顺时针旋转 ++m_head; if (m_head == m_capacity) { m_head = 0; } --m_size; return true; } /** Deletes an item from the rear of Deque. Return true if the operation is successful. */ bool deleteLast() { if (isEmpty()) return false; --m_tail; if (m_tail < 0) { m_tail = m_capacity - 1; } --m_size; return true; } /** Get the front item from the deque. */ int getFront() { if (isEmpty()) return -1; return m_arr[m_head]; } /** Get the last item from the deque. */ int getRear() { if (isEmpty()) return -1; //注意:最后元素的索引是m_tail的上一个元素 int index = m_tail - 1; if (index < 0) { index = m_capacity - 1; } return m_arr[index]; } /** Checks whether the circular deque is empty or not. */ bool isEmpty() { return m_size == 0; } /** Checks whether the circular deque is full or not. */ bool isFull() { return m_size == m_capacity; } private: int* m_arr; int m_head; int m_tail; int m_size; //元素个数 int m_capacity; //容量 固定长度 }; /** * Your MyCircularDeque object will be instantiated and called as such: * MyCircularDeque* obj = new MyCircularDeque(k); * bool param_1 = obj->insertFront(value); * bool param_2 = obj->insertLast(value); * bool param_3 = obj->deleteFront(); * bool param_4 = obj->deleteLast(); * int param_5 = obj->getFront(); * int param_6 = obj->getRear(); * bool param_7 = obj->isEmpty(); * bool param_8 = obj->isFull(); */
设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。
注意:
总人数少于1100人。
示例
输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
核心思路非常简单:
先排身高更高的,这是要防止后排入人员影响先排入人员位置
每次排入新人员[h,k]时,已处于队列的人身高都>=h,所以新排入位置就是people[k]
## 身高降序排列,身高相同,人数升序排列,然后按照新的排序,人数作为排位位置进行插入;
分别用两种方式实现插入,list效率更高;
class Solution { public: vector<vector<int>> reconstructQueue(vector<vector<int>>& people) { /* * 1. 首先考虑身高相同的情况: 例如 [7,0] 和 [7,1], [7,0]一定在[7,1]前面 * 2. 然后接着考虑身高不同的情况, 身高低的相对于身高高的人来说, 是看不见的, 不影响k值 * 3. 所以可以按照 [h,k] h大小排序, h相同按照k的大小排序 * 4. [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] --- >->-> * [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]] * 5. 按照 [h,k]中k的位置在容器中插入: * 5.1 -> [7,0], [7,1] * 5.2 -> [7,0], [6,1], [7,1] * 5.3 -> [5,0], [7,0], [6,1], [7,1] * 5.4 -> [5,0], [7,0], [5,2], [6,1], [7,1] * 5.5 -> [5,0], [7,0], [5,2], [6,1], [4,4], [7,1] */ //STL排序 sort 用法:sort(first_pointer,first_pointer+n,cmp),默认为升序,若要使用降序,自行写cmp 函数 auto cmp = [](const vector<int>& v1, const vector<int>& v2) { //若第一个元素相同,则按照第二个元素升序排列,否则,按照第一个元素降序排列 return v1[0] == v2[0] ? v1[1] < v2[1] : v1[0] > v2[0]; }; //标准模版库算法实例化,身高降序排列,人数升序排列 sort(people.begin(), people.end(), cmp); //按照顺序重新插入,按照人数的位置进行插入 //方法一:list 实现,效率更高: // list<vector<int>> sort_list; // for (auto item : people) { // auto iter = sort_list.begin(); // //advance迭代器就是将迭代器it,移动n位。如果it是随机访问迭代器,那么函数进行1次运算符计算操作,否则函数将对迭代器进行n次迭代计算操作 // advance(iter, item[1]); // sort_list.insert(iter, item); // } // //list转换为vector // vector<vector<int>> ret(sort_list.begin(), sort_list.end()); // return ret; //方法二:vector 实现: vector<vector<int>> res; for(auto item:people){ res.insert(res.begin()+item[1],item); } return res; } };
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.min(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.min(); --> 返回 -2.
思路:用一个辅助栈来专门维护栈的最小值
class MinStack { public: /** initialize your data structure here. */ MinStack() { } stack<int>s; stack<int>Min; //push操作,如果最小栈为空,则将元素入栈,否则比较栈顶元素和该元素大小确定是否入栈 void push(int x) { s.push(x); if(Min.empty()||x<=Min.top())Min.push(x); } //pop操作,如果原始栈不为空并且栈顶元素和和最小栈顶元素相同,则都出栈,否则只对原始栈出栈 void pop() { if(!s.empty()){ if(s.top()==Min.top())Min.pop(); s.pop(); } } //top操作不变 int top() { return s.top(); } // min 函数就是返回最小栈栈顶元素 int min() { return Min.top(); } }; /** * Your MinStack object will be instantiated and called as such: * MinStack* obj = new MinStack(); * obj->push(x); * obj->pop(); * int param_3 = obj->top(); * int param_4 = obj->min(); */
栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。
该栈支持如下操作:push
、pop
、peek
和 isEmpty
。当栈为空时,peek
返回 -1。
示例1:
输入: ["SortedStack", "push", "push", "peek", "pop", "peek"] [[], [1], [2], [], [], []] 输出: [null,null,null,1,null,2]
示例2:
输入: ["SortedStack", "pop", "pop", "push", "pop", "isEmpty"] [[], [], [], [1], [], []] 输出: [null,null,null,null,null,true]
解题思路:
利用辅助栈可以保证每次插入新元素的适合s1都是有序的,
比如 s1 = {1,5,6,8,9},插入 7
先把 9和8 插入 s2, s2 = {9,8}
再把 7 插入 s1, s1 = {1,5,6,7}
再把 s2 中数字插入 s1, s1 = {1,5,6,7,8,9}
这样思路最简单但是也比较麻烦
class SortedStack { public: stack<int> s1, s2; SortedStack() { } //比较栈顶元素和将要入栈的元素,如果栈顶元素小于将入栈的元素,则出栈到辅助栈 void push(int val) { while(!s1.empty() && s1.top() < val){ s2.push(s1.top()); s1.pop(); } s1.push(val); //然后将辅助栈元素再入栈道原始栈,此时栈有序,且栈顶为最小元素 while(!s2.empty()){ s1.push(s2.top()); s2.pop(); } } void pop() { if(!s1.empty()) s1.pop(); } int peek() { if(!s1.empty()) return s1.top(); return -1; } bool isEmpty() { return s1.empty(); } }; /** * Your SortedStack object will be instantiated and called as such: * SortedStack* obj = new SortedStack(); * obj->push(val); * obj->pop(); * int param_3 = obj->peek(); * bool param_4 = obj->isEmpty(); */
另一种解法,维护两个栈,原栈为降序,辅助栈为升序
比如s1 = {8, 7, 3} s2 = {}
插入 5,因为比s1.top大,把3插入s2中,然后 5插入 s1 中
s1 = {8,7,5} s2={3}
这样既能保证 s1 中的元素一定大于 s2 中元素,也可以使得两个栈都是按顺序排列
不必要像第一种解法一样需要在push的时候就把 s2 中元素重新加入到 s1 中去
class SortedStack { public: stack<int>s1;//原栈为降序 stack<int>s2;//辅助栈为升序 SortedStack() { } void push(int val) { while(!s2.empty() && s2.top() > val){//辅助栈中存在比val大的值 s1.push(s2.top()); s2.pop(); } while(!s1.empty() && s1.top() < val){//原栈中有比val小的值 s2.push(s1.top()); s1.pop(); } s1.push(val); } void pop() { while(!s2.empty()){//清空辅助栈 s1.push(s2.top()); s2.pop(); } if(!s1.empty()) s1.pop(); } int peek() { while(!s2.empty()){//清空辅助栈 s1.push(s2.top()); s2.pop(); } if(!s1.empty()) return s1.top(); else return -1; } bool isEmpty() { return s1.empty() && s2.empty(); } }; /** * Your SortedStack object will be instantiated and called as such: * SortedStack* obj = new SortedStack(); * obj->push(val); * obj->pop(); * int param_3 = obj->peek(); * bool param_4 = obj->isEmpty(); */
20、有效括号:
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: "()"
输出: true
示例 2:
输入: "()[]{}"
输出: true
示例 3:
输入: "(]"
输出: false
示例 4:
输入: "([)]"
输出: false
示例 5:
输入: "{[]}"
输出: true
思路:用栈实现,左括号入栈,右括号弹栈匹配
class Solution { public: bool isValid(string s) { int len = s.size(); if(len%2!=0){ return false; } stack<char> st; st.push(s[0]); for(int i=1;i<len;i++){ if(s[i]=='('||s[i]=='{'||s[i]=='['){ st.push(s[i]); }else if(!st.empty()){ if(s[i]==']'){ if(st.top()=='['){ st.pop(); }else{ return false; } } if(s[i]==')'){ if(st.top()=='('){ st.pop(); }else{ return false; } } if(s[i]=='}'){ if(st.top()=='{'){ st.pop(); }else{ return false; } } } else if(st.empty()){ return false; } } if(st.empty()){ return true; } return false; } };
316. 去除重复字母
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
注意:该题与 1081 https://leetcode-cn.com/problems/smallest-subsequence-of-distinct-characters 相同
示例 1:
输入:s = "bcabc"
输出:"abc"
示例 2:
输入:s = "cbacdcbc"
输出:"acdb"
思路:
贪心思路:如果当前字符的左边字符比当前字符大,而且左边字符在当前字符的右边还会出现,那就先舍弃掉左边字符。
单调栈思路:
class Solution { public: string res = "0";//先加一个0是为了方便判断,类似链表的哑节点 string removeDuplicateLetters(string s) { int count[26] = {0};//计数器,记录每个字母出现的次数 int visited[26] = {0};//用来记录结果数组里面是否已经包含某个字符,避免重复 for(int i = 0; i < s.size(); ++i) ++count[s[i] - 'a']; for(const auto &c : s){ --count[c - 'a']; if(visited[c - 'a'] == 1) continue;//已经包含了 while(c < res.back() && count[res.back() - 'a']){ visited[res.back() - 'a'] = 0;//pop出去就不再包含了 res.pop_back(); } res += c; visited[c - 'a'] = 1;//包含了该字符 } return res.substr(1); } }; 不加0其实也差不多: class Solution { public: string res; string removeDuplicateLetters(string s) { int count[26] = {0};//计数器,记录每个字母出现的次数 int visited[26] = {0};//用来记录结果数组里面是否已经包含某个字符,避免重复 for(int i = 0; i < s.size(); ++i) ++count[s[i] - 'a']; for(const auto &c : s){ --count[c - 'a']; if(visited[c - 'a'] == 1) continue;//已经包含了 while(!res.empty() && c < res.back() && count[res.back() - 'a']){ visited[res.back() - 'a'] = 0;//pop出去就不再包含了 res.pop_back(); } res += c; visited[c - 'a'] = 1;//包含了该字符 } return res; } };