栈与队列相关题目

一、用栈实现队列         LeetCode链接

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

说明:

  • 你 只能 使用标准的栈操作 —— 也就是只有 push to toppeek/pop from topsize, 和 is empty 操作是合法的。
  • 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

示例 1:

输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]

解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

提示:

  • 1 <= x <= 9
  • 最多调用 100 次 pushpoppeek 和 empty
  • 假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)  

思路

栈是先进后出,队列是先进先出,用两个栈实现队列,一个管输入stackIn,一个管输出stackOut。要实现队列的push(),pop(),peek()和empty()方法,

对于push方法直接把元素压到stackIn栈中即可.

pop()方法是本题关键,队列是先进先出,加入先进入了1,2,3那么出去也是1,2,3.但是在栈中直接输出为3,2,1.那不妨把这三个数依次出栈并压到out栈中,那么out栈中就为1,2,3,栈头为1.此时调用out栈的pop()方法即可。

class MyQueue {
    Stack<Integer> stackIn;
    Stack<Integer> stackOut;
    public MyQueue() {
        stackIn = new Stack<>();
        stackOut = new Stack<>();
    }

    public void push(int x) {
        stackIn.push(x);
    }

    public int pop() {
        if(stackOut.empty()){ //首先保证out栈为空,否则直接pop即可
//     每次调用pop都得把in栈的元素全都压入out栈,否则数据会混乱
            while (!stackIn.empty()){ 
                stackOut.push(stackIn.pop());
            }
        }
        return stackOut.pop();
    }

    public int peek() {
//    求队头元素,先调用本队列的pop方法,保存数据后再压入栈中即可
        int elem = this.pop(); 
        stackOut.push(elem);
        return elem;
    }

    public boolean empty() {
        if(stackIn.empty() && stackOut.empty())
            return true;
        else
            return false;
        //return (stackIn.empty() && stackOut.empty());
    }
}

二、用队列实现栈         LeetCode链接

请你仅使用 两/一 个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty)。

实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

注意:

  • 你只能使用队列的基本操作 —— 也就是 push to backpeek/pop from frontsize 和 is empty 这些操作。
  • 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

示例:

输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]

解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

提示:

  • 1 <= x <= 9
  • 最多调用100 次 pushpoptop 和 empty
  • 每次调用 pop 和 top 都保证栈不为空

思路

(1)两个队列实现

用两个队列实现时,一个队列用于存放数据,另一个作为中转队列。当队列中没有元素时直接入队,当有多个时,先把要入的数据放到另一个队列中,然后把本队列中的数据依次出队并进入到另一个队列中,此时队头元素即为新进的元素,然后把两队列的地址指针交换一下即可。

对于pop方法和peek方法,直接调用队列的对应方法即可,因为此时队列中的元素排列顺序和用栈操作时一样。

(2)一个队列实现

用一个队列操作,也是模拟数据在栈中的顺序。当队列中没有元素时直接入队,当有多个时,先把数据入队,然后把它前面的数据依次出队并进入这个队列(总共执行size()- 1 次)。此时队列中数据的顺序和用栈操作时一样。

public class MyStack {
    Queue<Integer> queue1;
//  Queue<Integer> queue2;
    public MyStack() {
        queue1 = new LinkedList<>();
//      queue2 = new LinkedList<>();
    }

    public void push(int x) {
//        queue2.offer(x);
//        while (!queue1.isEmpty()){
//            queue2.offer(queue1.poll());
//        }
//        Queue<Integer> q3 = queue1;
//        queue1 = queue2;
//        queue2 = q3;

        queue1.offer(x);
        for (int i = 0; i < queue1.size() - 1; i++) {
            int elem = queue1.poll();
            queue1.offer(elem);
        }

    }

    public int pop() {
       return queue1.poll();
    }

    public int top() {
        return queue1.peek();
    }

    public boolean empty() {
        if (queue1.isEmpty())
            return true;
        else
            return false;
//      return queue1.empty();
    }
}

三、有效的括号        LeetCode链接

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成

思路

本题考虑括号的配对。对于左括号,依次把它们对应的右括号压入栈中,遍历到右括号时,拿它和栈顶元素比较。

return false有好几种情况,(1)左括号多,即字符串遍历结束后栈不为空。(2)右括号多,即当字符串还未遍历完时栈已为空。(3)括号不匹配,当遍历的右括号和栈顶元素不一样时,表明该右括号和其对应位置上的左括号不匹配。

当以上可能都没发生,即字符串遍历结束后栈恰为空,且在比较过程中,遍历的字符和栈顶元素的一样,则返回ture。

public class Brackets {
    public boolean isValid(String s) {
        if (s.length() % 2 != 0)
            return false;

        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < s.length(); i++) {
            if ('(' == s.charAt(i))
                stack.push(')');
            else if ('[' == s.charAt(i))
                stack.push(']');
            else if ('{' == s.charAt(i))
                stack.push('}');
            else if (stack.empty() || !stack.peek().equals(s.charAt(i)))
                return false;
            else
                stack.pop();

        }
        if (stack.empty())
            return true;
        else
            return false;

    }
}

四、删除字符串中的所有相邻重复项        LeetCode链接

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

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

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

示例:

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

思路

本题用栈做很简单,遍历字符串,每次取一个字符,当栈为空时直接入栈,否则就拿该字符和栈顶元素比较,若相等则出栈一次,否则就把该字符压入栈中,直到字符串遍历完。

public class DeleteSame {
    public String removeDuplicates(String s) {
        Deque<Character> myStack = new LinkedList<>();

        for (int i = 0; i < s.length(); i++) {
            if ( myStack.isEmpty() || myStack.peek() != s.charAt(i)){
                myStack.push(s.charAt(i));
            }else
                myStack.pop();
        }
        String s1 = "";
        while (myStack.size() != 0){
            s1 = myStack.pop() + s1;
        }
        return s1;
    }
}

五、 逆波兰表达式求值         LeetCode链接

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +-*/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:

输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

思路

逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。

平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。

该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。

  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。

本题的做法即为遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。

public class EvalRPN {
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList<>();
        for (int i = 0; i < tokens.length; i++) {
            if ("+".equals(tokens[i]))
                stack.push(stack.pop() + stack.pop());
            else if ("-".equals(tokens[i]))
                stack.push(-stack.pop() + stack.pop());
            else if ("*".equals(tokens[i]))
                stack.push(stack.pop() * stack.pop());
            else if ("/".equals(tokens[i])){
                int num1 = stack.pop();
                int num2 = stack.pop();
                stack.push(num2 / num1);
            }
            else
                stack.push(Integer.valueOf(tokens[i]));
        }
        return stack.pop();

    }
}

六、滑动窗口最大值        LeetCode链接

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

返回 滑动窗口中的最大值 

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

输入:nums = [1], k = 1
输出:[1]

思路

我们需要一个队列,这个队列呢,放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是什么。每次窗口移动的时候就调用一次pop()和push(),让队列中的元素跟着窗口里的元素一起变化,之后再调用getMaxValue()返回最大值。

然后在分析一下,队列里的元素一定是要排序的,而且要最大值放在出队口,要不然怎么知道最大值呢。

但如果把窗口里的元素都放进队列里,窗口移动的时候,队列需要弹出元素。

那么问题来了,已经排序之后的队列 怎么能把窗口要移除的元素(这个元素可不一定是最大值)弹出呢。

其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队里里的元素数值是由大到小的。

那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。不要以为实现的单调队列就是 对窗口里面的数进行排序,如果排序的话,那和优先级队列又有什么区别了呢。

设计单调队列的时候,pop,和push操作要保持如下规则:

  1. pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
  2. push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止

保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。

(1)自定义单调队列

public class MyQueue {
    Deque<Integer> deque = new LinkedList<>();

    void push(int val){
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,
// 直到push的数值小于等于队列入口元素的数值为止。
// 这样就保持了队列里的数值是单调从大到小的了。
        while (!deque.isEmpty() && val > deque.peekLast()){
            deque.pollLast();
        }
        deque.offerLast(val);
    }

    void pop(int val){
// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,
// 如果相等则弹出。
// 同时pop之前判断队列当前是否为空。
        if (!deque.isEmpty() && val == deque.peekFirst()){
            deque.pollFirst();
        }
    }

    int getMaxValue(){
        return deque.peekFirst();
    }
}

(2)题解 

public class MaxSlidingWindow {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int len = nums.length - k + 1;
        int[] arr = new int[len];
        MyQueue myQueue = new MyQueue();

//      先把前k个元素放入队列中,
//      因为刚开始只需要push和getMaxValue操作,不需要pop
        for (int i = 0; i < k; i++) {
            myQueue.push(nums[i]);
        }
        int count = 0;
        arr[count++] = myQueue.getMaxVlue();

        for (int i = k; i < nums.length; i++) {
            myQueue.pop(nums[i - k]);
            myQueue.push(nums[i]);
            arr[count++] = myQueue.getMaxVlue();
        }
        return arr;

    }
}

总结

1.对于栈和队列来说,要了解清楚其底层代码是如何书写的,这样用的时候才能得心应手。

2.栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中是不是连续分布。

3.要理解单调队列和优先级队列。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值