栈和队列题目汇总

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;
        }
    }
};
/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */
 
 
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();
    }
};
/**
 * 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();
 */

 

 

 

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();
 */

 

5、406. 根据身高重建队列

 

设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(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;

    }
};

  

 

6、剑指 Offer 30. 包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 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();
 */

  

 

7、面试题 03.05. 栈排序

栈排序。 编写程序,对栈进行排序使最小元素位于栈顶。最多只能使用一个其他的临时栈存放数据,但不得将元素复制到别的数据结构(如数组)中。

该栈支持如下操作:pushpoppeek 和 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;
    }
};

  

 

posted @ 2020-09-18 18:14  静悟生慧  阅读(340)  评论(0编辑  收藏  举报