算法二 栈、队列、堆(Java实现)

448 阅读4分钟

刷题时间:2021.7.20-2021.7.22

栈:先进后出线性表

push()把项压入堆栈顶部

pop()移除堆栈顶部的对象,并作为此函数的值返回该对象

peek()查看堆栈顶部的对象,但不从堆栈中移除它

队列:先进先出线性表

offer()方法往队列添加元素如果队列已满直接返回false,队列未满则直接插入并返回true

poll()方法取出并删除队头的元素,当队列为空,返回null

peek()方法直接取出队头的元素,并不删除

PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。PriorityQueue保存队列元素的顺序不是它们加入队列的顺序,而是他们的大小(默认poll的顺序是从小到大)。所以使用peek()、poll()函数取出队列中的元素时,不是取出最先存进去的元素而是所有元素中最小的元素。同时PriorityQueue不允许插入null元素,他存储的元素必须是可以比较的对象,否则要指明比较器。

PriorityQueue实现自定义比较器: PriorityQueue中的元素是从小到大顺序排列的,如果我希望里面的元素从大到小逆序排列可以这么写:

PriorityQueue pq = new PriorityQueue<>((a, b) -> (b - a));

二叉堆:最小(大)值先出的完全二叉树

1、LeetCode225用队列实现栈

思路:在STACK push元素时,利用临时队列调换元素次序

class MyStack {
    Queue<Integer> data_queue;
    Queue<Integer> temp_queue;

    /** Initialize your data structure here. */
    public MyStack() {
        data_queue = new LinkedList<Integer>();//data_queue数据队列存储元素的顺序就是栈存储元素的顺序
        temp_queue = new LinkedList<Integer>();//临时队列,利用该队列进行原始data_queue元素与新元素的次序调换
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        temp_queue.offer(x);
        while (!data_queue.isEmpty()) {
            temp_queue.offer(data_queue.poll());//将原队列内容offer进入临时队列
        }
        while(!temp_queue.isEmpty()){
            data_queue.offer(temp_queue.poll());//将临时队列内容offer进入数据队列
        }
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return data_queue.poll();//取出并删除队头的元素,当队列为空,返回null
    }
    
    /** Get the top element. */
    public int top() {
        return data_queue.peek();//直接取出队头的元素,并不删除
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return data_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();
 */

2、LeetCode232用栈实现队列

思路:在队列push元素时,利用临时栈调换元素次序

class MyQueue {
    Deque<Integer> temp_Stack;
    Deque<Integer> data_Stack;

    /** Initialize your data structure here. */
    public MyQueue() {
        temp_Stack = new LinkedList<Integer>();//设置临时栈用来调换数据栈中的元素次序
        data_Stack = new LinkedList<Integer>();
    }
    
    /** Push element x to the back of queue. */
    public void push(int x) {       
        while (!data_Stack.isEmpty()) {
            temp_Stack.push(data_Stack.pop());//将原数据栈内容push进入临时栈
        }
        temp_Stack.push(x);//将新数据push进入临时栈
        while(!temp_Stack.isEmpty()){
            data_Stack.push(temp_Stack.pop());//将临时栈内容push进入数据栈
        }
    }
    
    /** Removes the element from in front of queue and returns that element. */
    public int pop() {//弹出栈顶并返回头部元素
        return data_Stack.pop();
    }
    
    /** Get the front element. */
    public int peek() {//取出头部元素,并不删除
        return data_Stack.peek();
    }
    
    /** Returns whether the queue is empty. */
    public boolean empty() {
        return data_Stack.isEmpty();    
    }
}

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

3、LeetCode155最小栈

思路:用另一个栈,存储各个状态的最小值

class MinStack {
    Deque<Integer> xStack;
    Deque<Integer> minStack;

    public MinStack() {
        xStack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
    }
    
    public void push(int x) {
        xStack.push(x);//将数据压入数据栈
        if(minStack.isEmpty()){
            minStack.push(x);
        }else{
            if(x > minStack.peek()){
                x = minStack.peek();
            }
            minStack.push(x);
        }
    }
    
    public void pop() {//同时弹出
        xStack.pop();
        minStack.pop();
    }
    
    public int top() {
        return xStack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

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

4、LeetCode946验证栈序列

思路:出栈结果存储在队列order中。按元素顺序,将元素push进入栈。每push一个元素,即检查是否与队列首部元素相同,若相同则弹出队列首元素和栈顶元素,直到两元素不同结束。若最终栈为空,说明序列合法,否则不合法。

class Solution {
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Stack<Integer> stack = new Stack<>();//stack为模拟栈序列
        int index = 0;
        for(int i = 0; i < pushed.length; i++){
            stack.push(pushed[i]);
            while(!stack.isEmpty() && stack.peek() == popped[index]){
                stack.pop();
                index++;
            }
        }
        return stack.isEmpty();
    }
}

5、LeetCode224基本栈序列

image.png

class Solution {
    public int calculate(String s) {
        Stack<Integer> stack = new Stack<Integer>();
        // sign 代表正负
        int sign = 1, res = 0;
        int length = s.length();
        for (int i = 0; i < length; i++) {
            char ch = s.charAt(i);
            if (Character.isDigit(ch)) {
                int cur = ch - '0';//将字符串转成数字
                while (i + 1 < length && Character.isDigit(s.charAt(i + 1)))//数字不止一位
                    cur = cur * 10 + s.charAt(++i) - '0';
                res = res + sign * cur;
            } else if (ch == '+') {
                sign = 1;
            } else if (ch == '-') {
                sign = -1;
            } else if (ch == '(') {
                stack.push(res);
                res = 0;
                stack.push(sign);
                sign = 1;
            } else if (ch == ')') {
                res = stack.pop() * res + stack.pop();
            }
        }
        return res;
    }
}

6、LeetCode215数组中的第K个最大元素

思路:维护一个K大小的最小堆,堆中元素个数小于K时,新元素直接进入堆;否则,当堆顶小于新元素时,弹出堆顶,将新元素加入堆。 解释:由于堆是最小堆,堆顶是堆中最小元素,新元素都会保证比堆顶小(否则新元素替换堆顶),故堆中K个元素是已扫描的元素里最大的K个;堆顶即为第K大的数。

class Solution {
    public int findKthLargest(int[] nums, int k) {//最小堆
        PriorityQueue<Integer> data_queue = new PriorityQueue<Integer>();//优先级队列
        for(int i = 0;i < nums.length;i++){
            if(data_queue.size() < k){
                data_queue.offer(nums[i]);
            }else if(data_queue.peek()<nums[i]){//如果堆顶比新元素小,弹出堆顶,offer进入新元素
                data_queue.poll();
                data_queue.offer(nums[i]);
            }
        }
        return data_queue.peek();
    }
}

7、LeetCode295数据流的中位数

在数据流中,数据会不断涌入结构中,那么也就面临着需要多次动态调整以获得中位数。 因此实现的数据结构需要既需要快速找到中位数,也需要做到快速调整。首先能想到就是二叉搜索树,在平衡状态下,树顶必定是中间数,然后再根据长度的奇偶性决定是否取两个数。此方法效率高,但是手动编写较费时费力。根据只需获得中间数的想法,可以将数据分为左右两边,一边以最大堆的形式实现,可以快速获得左侧最大数, 另一边则以最小堆的形式实现。其中需要注意的一点就是左右侧数据的长度差不能超过1。 这种实现方式的效率与AVL平衡二叉搜索树的效率相近,但编写更快。

思路:动态维护一个最大堆与一个最小堆,最大堆存储一半数据,最小堆存储一半数据, 维持最大堆的堆顶比最小堆的堆顶小。

class MedianFinder {
    PriorityQueue<Integer> small_queue;//最小堆
    PriorityQueue<Integer> big_queue;//最大堆
    /** initialize your data structure here. */
    public MedianFinder() {
        small_queue = new PriorityQueue<Integer>();
        big_queue = new PriorityQueue<Integer>((a, b) -> b - a);
    }
    
    public void addNum(int num) {
        if(big_queue.isEmpty()){
            big_queue.offer(num);
            return;
        }
        if(big_queue.size()==small_queue.size()){
            if(num<big_queue.peek()){
                big_queue.offer(num);
            }else{
                small_queue.offer(num);
            }
        }else if(big_queue.size()>small_queue.size()){
            if(num>big_queue.peek()){
                small_queue.offer(num);
            }else{
                small_queue.offer(big_queue.peek());
                big_queue.poll();
                big_queue.offer(num);
            }
        }else{
            if(num<small_queue.peek()){
                big_queue.offer(num);
            }else{
                big_queue.offer(small_queue.peek());
                small_queue.poll();
                small_queue.offer(num);
            }
        }
    }

    public double findMedian() {
        if (big_queue.size() == small_queue.size()){
            return (big_queue.peek() + small_queue.peek()) / 2.0;
        }
        else if(big_queue.size() > small_queue.size()){
            return big_queue.peek();
        }
        return small_queue.peek();
    }
}
    
/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */