跳转至

栈和队列

Java中常用的栈和队列

// Stack , 实现如下:
Stack<Integer> stack = new Stack<Integer>();
// 方法:
// 1.pop() 弹出栈顶元素, 返回值为栈顶元素
// 2.push(int x) 将一个元素压入栈的顶部
// 3.peek() 返回栈顶元素, 但是并不移除
// 4. empty() 判断是否为空

队列

队列为 先进先出

// Queue, 实现如下:
Queue<Integer> queue = new LinkedList<>();

// 方法:
// 1.poll() 移除并返回队头元素
// 2.offer(int x) 在队尾添加元素(队列的入口在队尾)
// 3.peek()  返回队头元素
// 4.isEmpty() 判断是否为空

Deque

Deque是双端队列(Double Ended Queue)的缩写,它允许在队列的两端进行插入和删除操作。 - 可以像栈一样在头部进行插入和删除(后进先出,LIFO) - 可以像队列一样在尾部进行插入,头部进行删除(先进先出,FIFO)

总结:

Deque<Integer> deque = new LinkedList<>();

// 1. 实现和队列相同的效果
// add(int x) 添加元素到队列尾部
// poll() 弹出并返回队头元素
// peek() 返回队头元素
// isEmpty() 判断是否为空

// 2. 实现和栈相同的效果
// push(int x) 在队头添加元素 (相当于把元素压入栈顶 队头是出口, 相当于栈顶)
// pop() 弹出并返回栈顶(队头)元素
// peek() 返回栈顶(队头)元素
// isEmpty() 判断是否为空

// 3. 实现双端队列
// getLast() 获得队列的最后一个元素 (如果是单端的队列无法获取)
// peekLast() 获得队列的最后一个元素
// removeLast() 移除并返回队列的最后一个元素

1. 用栈实现队列

link : LeetCode 232

思路: 使用两个栈,一个入栈,一个出栈,当需要 poppeek 元素的时候,检查出栈是否为空,如果为空将入栈的元素都弹出到出栈中, 然后出栈进行 poppeek, 从而保证了元素的先进先出

class MyQueue {



    Stack<Integer> stackIn;

    Stack<Integer> stackOut;



    public MyQueue() {

        stackIn = new Stack<Integer>();

        stackOut = new Stack<Integer>();



    }

    public void push(int x) {

        stackIn.push(x);



    }

    public int pop() {

        if(stackOut.isEmpty()){

            while(!stackIn.isEmpty()){

                stackOut.push(stackIn.pop());

            }

        }

        return stackOut.pop();



    }

    public int peek() {

         if(stackOut.isEmpty()){

            while(!stackIn.isEmpty()){

                stackOut.push(stackIn.pop());

            }

        }

        return stackOut.peek();

    }

    public boolean empty() {

        if (stackIn.isEmpty() && stackOut.isEmpty())

            return true;

        return false;



    }

}



/**

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

 * boolean param_4 = obj.empty();

 */

2. 用队列实现栈

link : LeetCode 225

思路:

方法一:使用两个队列,以队列1对核心,如果有元素进入队列,先把队列1中的元素全部放在队列2中,然后元素进入队列,再把队列2中的元素添加到队列1中

class MyStack {



    Queue<Integer> queue1;

    Queue<Integer> queue2;



    public MyStack() {



        queue1 = new LinkedList<>();

        queue2 = new LinkedList<>();

    }

    public void push(int x) {

        // 有元素进入队列,因为栈是后进先出, 因此需要将x放入队列的头部

        // 先将队列1中的元素放入队列2中

        while(!queue1.isEmpty()){

            queue2.offer(queue1.poll());

        }

        // 将元素放入 queue1 中, 此时在队列最前方

        queue1.offer(x);

        // 将队列2中的元素放入队列1中

        while(!queue2.isEmpty()){

            queue1.offer(queue2.poll());

        }



    }

    public int pop() {

        // 弹出元素, 直接弹出队列1中的元素

        return queue1.poll();



    }

    public int top() {

        // 栈顶元素, 也是队列1中的第一个元素

        return queue1.peek();



    }

    public boolean empty() {

        return queue1.isEmpty();

    }

}



/**

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

 * boolean param_4 = obj.empty();

 */

方法二:使用一个队列实现

当有元素进入队列时, 直接入队,然后将该元素前面的元素都放在该元素的后面(从而新来的元素变成了最前面的元素,从而实现后入先出)

class MyStack {



    Queue<Integer> queue;



    public MyStack() {

        queue = new LinkedList<>();

    }

    public void push(int x) {

        queue.offer(x);

        int len = queue.size();

        while(len-- > 1){

            queue.offer(queue.poll());

        }

    }

    public int pop() {

        // 弹出元素, 直接弹出队列1中的元素

        return queue.poll();



    }

    public int top() {

        // 栈顶元素, 也是队列1中的第一个元素

        return queue.peek();



    }

    public boolean empty() {

        return queue.isEmpty();

    }

}



/**

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

 * boolean param_4 = obj.empty();

 */

3. 有效的括号

link : LeetCode 20

思路: 使用栈来实现, 当遇到左括号,在栈中放入有括号,继续遍历字符串,当新的字符和栈顶字符对应(新的字符是对应的右括号),则弹出栈顶 可能遇到的情况: - 新的字符和栈顶不一样,括号不匹配,返回 false - 有新的字符,但是栈为空(说明右括号太多) - 无新的字符,但是栈不为空(说明左括号太多)

class Solution {

    public boolean isValid(String s) {

        Stack<Character> stack = new Stack<>();

        char ch;

        for (int i=0; i<s.length(); i++){

            ch = s.charAt(i);

            if (ch == '(') stack.push(')');

            else if(ch == '{') stack.push('}');

            else if(ch == '[') stack.push(']');

            // 判断是否匹配

            else{

                // 如果栈已经为空, 右括号太多

                if (stack.isEmpty()) return false;

                // 新的字符与栈顶不匹配

                else if(stack.peek() != ch) return false;

                else {

                    stack.pop();

                }

            }

        }



        // 遍历字符串结束, 判断栈是否为空

        return stack.isEmpty();

    }

}

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

link : LeetCode 1047

思路: 使用栈来存储遍历过的字符,当遍历新的字符时,判断新的字符和栈顶元素是否相等,如果相等,说明他们是相邻的重复项,则弹出栈顶元素;继续遍历下一个字符

class Solution {

    public String removeDuplicates(String s) {

        StringBuffer resBuffer = new StringBuffer();

        Stack<Character> stack = new Stack<>();

        char ch;

        for (int i=0; i<s.length(); i++){

            ch = s.charAt(i);

            // 如果栈是空的, 将字符加入栈中

            if (stack.isEmpty()){

                stack.push(ch);

                continue;

            }else{

                // 如果栈不为空, 判断新加入的元素和栈顶元素是否相等, 如果相等则弹出

                if (ch == stack.peek()) stack.pop();

                else stack.push(ch);

            }

        }



        // 弹出栈中的元素

        while(!stack.isEmpty()){

            resBuffer.append(stack.pop());

        }

        // 弹出的元素是逆序的, 因此需要反转字符串

        resBuffer.reverse();

        return resBuffer.toString();



    }

}

5. 逆波兰表达式求值

link : LeetCode 150

思考: 遍历字符串,使用栈存储数字,当遇到数字时,压入栈中,当遇到运算符时,连续弹出两个数字进行运算,并把运算结果压入栈中。遍历结束,栈顶元素就是最后的结果

注意:连续弹出两个数字,第一个弹出的是 a, 第二个弹出的是 b, 如果是减法运算 则是 \(b-a\), 如果是除法运算则是 \(b/a\)

class Solution {

    public int evalRPN(String[] tokens) {

        Stack<Integer> stack = new Stack<>();



        String temp;

        for (int i=0; i<tokens.length; i++){

            temp = tokens[i];



            if (temp.equals("+")) {

                int a = stack.pop();

                int b = stack.pop();

                stack.push(b+a);

            }else if  (temp.equals("-")) {

                int a = stack.pop();

                int b = stack.pop();

                stack.push(b-a);

            }else if  (temp.equals("*")) {

                int a = stack.pop();

                int b = stack.pop();

                stack.push(a*b);

            }else if  (temp.equals("/")) {

                int a = stack.pop();

                int b = stack.pop();

                stack.push(b/a);

            }else{

                stack.push(Integer.parseInt(temp));

            }

        }



        return stack.pop();

    }

}

6.滑动窗口最大值

思路: 使用单调队列

因为要求出每一个滑动窗口的最大值,因此队列头存储对应的最大值

  • 当窗口开始滑动,如果丢弃的值和队列头的值相等,则pop队列头
  • 比较新进入窗口的值和 队列末尾的元素值,如果新进入的元素值比末尾的元素值大,则删除末尾的元素,一直到队列末尾的元素值大于等于 新加入的元素值为止
  • 如果新加入的元素值比对头大,则清空原队列,将新加入的元素值作为队头
  • 从而保证 队列头 存储最大值
class Solution {

    class MyDeque{

        Deque<Integer> deque = new LinkedList<>();



        // 添加元素

        void add(int x){

            // 确保添加的元素的前面的元素都大于x, 否则删除前面的元素

            while(!deque.isEmpty() && x > deque.getLast()){

                deque.removeLast();

            }

            // 把前面小于 x 的元素全部删除

            // 将x添加到队尾

            deque.add(x);

        }

        // 弹出元素

        void poll(int x){

            // 判断元素是否弹出

            // 如果滑动窗口舍弃的元素 和队头元素相等, 则舍弃

            if (!deque.isEmpty() && x == deque.peek())

                // 弹出队头元素

                deque.poll();

        }



        // 获得队头元素

        int peek(){

            return deque.peek();

        }

    }





    public int[] maxSlidingWindow(int[] nums, int k) {

        // 判断数组长度是否大于1

        if (nums.length == 1) return nums;



        // 自定义队列

        MyDeque myDeque = new MyDeque();

        // 定义 resList 存储每个窗口的最大值

        List<Integer> resList = new ArrayList<>();

        // 先遍历第一个窗口值

        for (int i=0; i<k; i++){

            myDeque.add(nums[i]);

        }

        // 将第一个窗口的最大值(队头)添加到resList中

        resList.add(myDeque.peek());

        // 移动窗口

        for (int i=k; i<nums.length; i++){

            // 窗口移动会舍弃一个元素, 这个元素的下标是 i-k

            myDeque.poll(nums[i-k]);

            // 窗口移动会添加一个元素 i

            myDeque.add(nums[i]);



            // 将新窗口的最大值添加到 resList 中

            resList.add(myDeque.peek());

        }



        int res[] = new int[resList.size()];

        for (int i=0; i<resList.size(); i++){

            res[i] = resList.get(i);

        }



        return res;






    }

}

7. 前K个高频元素

堆的概念

"堆"通常指的是一种数据结构,用于存储和组织数据。堆通常是一个特定类型的树形数据结构,它满足堆属性。

其实 堆 就是一个二叉树,一般使用二叉树来实现堆

堆有两种主要类型:大顶堆和小顶堆。

手动实现一个堆

使用一维数组实现, 则当前节点下标为k, 左儿子节点为 2k,有儿子节点为 2k+1

堆的相关操作:

假设为小顶堆

  1. 插入一个数字 在末尾插入一个数字,并up,即如果插入的数字比父节点小, 则不断up向上, 直到大于等于父节点或者为二叉树的根节点 heap[++size]=x; up[size];
  2. 求集合当中的最小值 heap[1] 根节点即为最小值
  3. 删除最小值 因为根节点是最小值,但是根节点是在数组最前面,不容易删除 因此将根节点和最后的元素交换,然后size-1,再将根节点 down heap[1]=heap[size]; size--; down(1)
  4. 删除任意一个元素 方法和删除最小值类似 heap[k] = heap[size], size--; down(k),up(k)
  5. 修改任意一个元素 heap[k] = x, down(x), up(x)

前k个高频元素

思路: 1. 将每个元素出现的次数放入到 map 中, 其中 key 为元素值, value为出现的次数 2. 使用小根堆,遍历map,当新入堆的元素出现的次数大于 根节点的元素出现的次数, 则弹出(从而保证小根堆中保存的是出现次数前k多的元素)

class Solution {

    public int[] topKFrequent(int[] nums, int k) {

        Map<Integer, Integer> map = new HashMap<>();

        // key: 元素值 value: 出现的次数

        for (int num: nums){

            map.put(num, map.getOrDefault(num, 0) + 1);

        }

        // 使用优先级队列实现小根堆

        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair1[1]-pair2[1]);

        // 遍历map

        for (Map.Entry<Integer, Integer> entry:map.entrySet()){

            // 如果小根堆中的元素小于k个, 则直接加入到小根堆中

            if (pq.size() < k) pq.add(new int[]{entry.getKey(), entry.getValue()});

            else{

                // 如果新加入的元素 出现的次数比 小根堆中根节点元素出现的次数多

                if (entry.getValue() > pq.peek()[1]){

                    // 弹出根节点

                    pq.poll();

                    pq.add(new int[]{entry.getKey(), entry.getValue()});



                }

            }

        }



        int res[] = new int[k];

        int i=0;

        while(!pq.isEmpty()){

            res[i] = pq.poll()[0];

            i++;

        }



        return res;



    }

}