栈和队列题目汇总
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;
}
};

浙公网安备 33010602011771号