堆/栈/队列的相关算法

前言: \textcolor{Green}{前言:} 前言:

本专栏用于本人刷算法的过程。主要包含刷题中的感受以及知识点缺陷。对于学习者来说可以作为参考。
目前更新的算法内容会比较多,很多都是通过刷题来进行知识点的总结,其中部分来源于网络总结,如有侵权请联系。

文章存在时候,会随着博主的学习过程进行不间断更新,只增不减,请放心使用

堆/栈/队列的相关算法

  • 可以解决的经典问题
    • 队列
  • 算法实例
    • 1. 用两个栈实现队列
    • 2. 包含min函数的栈
    • 3. 有效括号序列
    • 4. 最小的K个数
    • 5. 寻找第K大
    • 6. 数据流中的中位数
  • 总结归纳
    • 队列

可以解决的经典问题

  1. 括号匹配是使用栈解决的经典问题。
  2. 字符串去重问题
  3. 逆波兰表达式问题

队列

  1. 滑动窗口最大值

算法实例

1. 用两个栈实现队列

题目来源:牛客网
等级: E a s y \textcolor{OrangeRed}{等级:Easy} 等级:Easy

题目描述

用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

数据范围: n ≤ 1000 n ≤ 1000 数据范围: n\le1000n≤1000 数据范围:n1000n1000
要求:存储n个元素的空间复杂度为 O(n) ,插入与删除的时间复杂度都是 O(1)

代码编写

方法1

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        if (stack1.isEmpty() && stack2.isEmpty()) {
            return -1;
        }
        if (!stack2.isEmpty()) {
            return stack2.pop();
        }
        while (!stack1.isEmpty()) {
            stack2.push(stack1.pop());
        }
        return stack2.pop();
    }
}

2. 包含min函数的栈

题目来源:牛客网
等级: E a s y \textcolor{OrangeRed}{等级:Easy} 等级:Easy

题目描述

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数,输入操作时保证 pop、top 和 min 函数操作时,栈中一定有元素。

此栈包含的方法有:
push(value):将value压入栈中
pop():弹出栈顶元素
top():获取栈顶元素
min():获取栈中最小元素

数据范围:操作数量满足 0 ≤ n ≤ 300 0≤n≤300 0n300 ,输入的元素满足 ∣ v a l ∣ ≤ 10000 ∣val∣≤10000 val∣≤10000
进阶:栈的各个操作的时间复杂度是 O(1) ,空间复杂度是 O(n)

示例:

输入:    ["PSH-1","PSH2","MIN","TOP","POP","PSH1","TOP","MIN"]
输出:    -1,2,1,-1
解析:
"PSH-1"表示将-1压入栈中,栈中元素为-1
"PSH2"表示将2压入栈中,栈中元素为2-1MIN”表示获取此时栈中最小元素==>返回-1
"TOP"表示获取栈顶元素==>返回2
"POP"表示弹出栈顶元素,弹出2,栈中元素为-1
"PSH1"表示将1压入栈中,栈中元素为1-1
"TOP"表示获取栈顶元素==>返回1MIN”表示获取此时栈中最小元素==>返回-1

示例1

输入:
 ["PSH-1","PSH2","MIN","TOP","POP","PSH1","TOP","MIN"]
复制
返回值:
-1,2,1,-1

代码编写

方法1

import java.util.Stack;

public class Solution {
    private Stack<Integer> stack = new Stack<>();
    private Stack<Integer> minStack = new Stack<>();
    
    public void push(int node) {
        stack.push(node);
        if (minStack.isEmpty()){
            minStack.push(node);
        } else {
            int min = minStack.peek();
            if (min > node) {
                minStack.push(node);
            } else {
                minStack.push(min);
            }
        }        
    }
    
    public void pop() {
        stack.pop();
        minStack.pop();
    }
    
    public int top() {
        if (stack.isEmpty()) {
            return -1;
        }
        return stack.peek();
    }
    
    public int min() {
        return minStack.peek();
    }
}

可以提炼的知识

  • 堆栈类peek()方法peek()方法在java.util包中使用。

  • peek()方法用于从此Stack中返回顶部元素,并且它不删除检索元素

  • peek()方法是一种非静态方法,只能通过类对象访问,如果尝试使用类名访问该方法,则会收到错误消息。

  • peek()方法在返回top元素时不会引发异常。

3. 有效括号序列

题目来源:牛客网
等级:简单 \textcolor{OrangeRed}{等级:简单} 等级:简单

题目描述

给出一个仅包含字符’(‘,’)‘,’{‘,’}‘,’[‘和’]',的字符串,判断给出的字符串是否是合法的括号序列
括号必须以正确的顺序关闭,"()“和”()[]{}“都是合法的括号序列,但”(]“和”([)]"不合法。

数据范围:字符串长度 0 ≤ n ≤ 100000 ≤ n ≤ 10000 0\le n \le 100000≤n≤10000 0n100000n10000
要求:空间复杂度 O(n),时间复杂度 O(n)

示例1

输入:"["
返回值:false

示例2

输入:"[]"
返回值:true

代码编写

方法1

import java.util.*;


public class Solution {
    /**
     * 
     * @param s string字符串 
     * @return bool布尔型
     */
    public boolean isValid (String s) {
        // write code here
        int len = s.length();
        if (len % 2 == 1) {
            return false;
        }
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < len; ++i) {
            char c = s.charAt(i);
            if (c == '(') {
                stack.push(')');
            } else if (c == '{') {
                stack.push('}');
            } else if (c == '[') {
                stack.push(']');
            } else if (stack.isEmpty() || stack.peek() != c) {
                return false;
            } else {
                stack.pop();
            }
        }
        if (stack.isEmpty()) {
            return true;
        }
        return false;
    }
}

4. 最小的K个数

题目来源:牛客网
等级: M e d i u m \textcolor{OrangeRed}{等级:Medium} 等级:Medium
使用方法:大根堆

题目描述

给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。
数据范围: ≤ k , n ≤ 100000 ≤ k , n ≤ 10000 ,数组中每个数的大小 0 ≤ v a l ≤ 10000 ≤ v a l ≤ 1000 \le k,n \le 100000≤k,n≤10000,数组中每个数的大小0 \le val \le 10000≤val≤1000 k,n100000k,n10000,数组中每个数的大小0val10000val1000
要求:空间复杂度 O(n) ,时间复杂度 O(nlogk)

示例1

输入:
[4,5,1,6,2,7,3,8],4 
返回值:[1,2,3,4]
说明:
返回最小的4个数即可,返回[1,3,2,4]也可以  

示例2

输入:[1],0
返回值:[]

示例3

输入:[0,1,2,1,2],3
返回值:[0,1,1]

代码编写

方法1

import java.util.*;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (k == 0 || input.length == 0) {
            return res;
        }

        // large root heap
        Queue<Integer> maxQueue= new PriorityQueue<>((v1, v2) -> v2 - v1);
        // Build large root heap
        for (int i = 0; i < k; ++i) {
            maxQueue.offer(input[i]);
        }

        for (int i = k; i < input.length; ++i) {
            // Small elements are piled
            if (maxQueue.peek() > input[i]) {
                maxQueue.poll();
                maxQueue.offer(input[i]);
            }
        }
        // Extract elements from the heap
        for (int i = 0; i < k; ++i) {
            res.add(maxQueue.poll());
        }
        return res;
    }
}

注意点

  • 为什么是大根堆?因为大根堆弹出来的元素是最大值可以与接下来的值进行比较,如果比最大值要小,那么就将该值弹出,并将比较的值offer进去,反之不动。
  • 大根堆的构建(注意要重写)
  • Queue maxQueue= new PriorityQueue<>((v1, v2) -> v2 - v1);

可以提炼的知识

  1. 如何用 PriorityQueue 实现大根堆 maxQueue?
  • PriorityQueue 默认小根堆大根堆需要重写比较器
  • 可以在 new PriorityQueue<>() 中的参数部分加入比较器。
  • 具体写法是:(v1, v2) -> v2 - v1
  • Queue 类中加入值是 offer() 方法,弹出是 poll() 方法。
    Queue<Integer> maxQueue= new PriorityQueue<>((v1, v2) -> v2 - v1);
    maxQueue.offer(1);
    maxQueue.poll();
    maxQueue.size();
    

5. 寻找第K大

题目来源:牛客网
等级: M e d i u m \textcolor{OrangeRed}{等级:Medium} 等级:Medium
使用方法:小根堆

题目描述

有一个整数数组,请你根据快速排序的思路,找出数组中第 k 大的数。

给定一个整数数组 a ,同时给定它的大小n和要找的 k ,请返回第 k 大的数(包括重复的元素,不用去重),保证答案存在。
要求:时间复杂度 O(nlogn)O(nlogn),空间复杂度 O(1)O(1)
数据范围: 0 ≤ n ≤ 10000 ≤ n ≤ 10001 ≤ K ≤ n 1 ≤ K ≤ n ,数组中每个元素满足 0 ≤ v a l ≤ 100000000 ≤ v a l ≤ 10000000 0\le n \le 10000≤n≤10001 \le K \le n1≤K≤n,数组中每个元素满足 0 \le val \le 100000000≤val≤10000000 0n10000n10001Kn1Kn,数组中每个元素满足0val100000000val10000000

示例1

输入:[1,3,5,2,2],5,3
返回值:2

示例2

输入:
[10,10,9,9,8,7,5,6,4,3,4,2],12,3
返回值:9
说明:
去重后的第3大是8,但本题要求包含重复的元素,不用去重,所以输出9 \

代码编写

方法1

import java.util.*;

public class Solution {
    public int findKth(int[] a, int n, int K) {
        // write code here
        if (K == 0 || n == 0) {
            return -1;
        }

        // Build small root heap with a sizeof K
        PriorityQueue<Integer> smallQueue = new PriorityQueue<>(K);
        for (int i = 0; i < K; ++i) {
            if (i > n) {
                break;
            }
            smallQueue.offer(a[i]);
        }
        for (int i = K; i < n; ++i) {
            if (smallQueue.peek() < a[i]) {
                smallQueue.poll();
                smallQueue.offer(a[i]);
            }
        }
        return smallQueue.peek();
    }
}

方法2

注意点

  • 小根堆不需要重写比较器。创建多大的小根堆就直接填入值就行
    // Build small root heap with a sizeof K
    PriorityQueue<Integer> smallQueue = new PriorityQueue<>(K);
    

可以提炼的知识

6. 数据流中的中位数

题目来源:牛客网
等级: M e d i u m \textcolor{OrangeRed}{等级:Medium} 等级:Medium
使用方法:小根堆+大根堆

题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

数据范围:$数据流中数个数满足 1 \le n \le 1000 \1≤n≤1000 ,大小满足 1 \le val \le 1000 \1≤val≤1000 $

进阶: 空间复杂度 O(n) , 时间复杂度 O(nlogn)

示例1

输入:[5,2,3,4,1,6,7,0,8]
返回值:"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "
说明:
数据流里面不断吐出的是5,2,3...,则得到的平均数分别为5,(5+2)/2,3...   

示例2

输入:[1,1,1]
返回值:"1.00 1.00 1.00 "

代码编写

方法1
参考大佬写法

[最大堆左边最大 leftMax] [右边最小rightMin 最小堆]

直接插入到对面的堆中,然后再转移到自己的堆中但是它的 时间复杂度提高, 每次都插入时间复杂度O(log n)

  1. 数据是偶数的时候 insert的数据进入 [右边,最小堆]中
    先把cur插入[最大堆|左边最大leftMax]中,
    然后把leftMax放入[右边最小rightMin|最小堆]中
    就可以保证: 左边 < 右边
  2. 数据是奇数的时候 insert的数据进入 [左边,最大堆]中
    先把cur插入[右边最小rightMin|最小堆]中,
    然后把rightMin放入[最大堆|左边最大leftMax]中
    就可以保证: 左边 < 右边
  3. 最后:
    如果是偶数:中位数mid= (leftMax+right)/2
    如果是奇数:中位数mid= leftMax
import java.util.*;

public class Solution {
    // Build large root heap, 
    private PriorityQueue<Integer> leftHeap = new PriorityQueue<>((v1, v2) -> v2 - v1);
    // Build small root heap
    private PriorityQueue<Integer> rightHeap = new PriorityQueue<>();
    // Judge whether it is odd
    private boolean isOdd = true;

    public void Insert(Integer num) {
        if (isOdd) {
            rightHeap.offer(num);
            leftHeap.offer(rightHeap.poll());
        } else {
            leftHeap.offer(num);
            rightHeap.offer(leftHeap.poll());
        }
        isOdd = !isOdd;
    }

    public Double GetMedian() {
        if (leftHeap.isEmpty()) {
            return 0.0;
        }
        if (!isOdd) {
            return leftHeap.peek() / 1.0;
        } else {
            return (leftHeap.peek() + rightHeap.peek()) / 2.0;
        }
    }
}

方法2

注意点

一点要保证两个堆中的数据数目差不能超过1,这样可以使中位数只会出现在两个堆的交接处;
保证大根堆所有数据都小于小根堆

可以提炼的知识

总结归纳

对于海量数据和流的数据,用最大堆和最小堆来管理

堆通常是一个可以被看做一棵树的数组对象。堆是非线性数据结构,相当于一维数组,有两个直接后继。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树;
  • 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

求第k大,就要用小根堆,每一次更新后,顶点时刻都是整个树中数值最小的节点,它的孩子都比他大,而我们求第k大,就构建一个只有 k 个节点的堆,最后返回堆顶。

    PriorityQueue<Integer> smallQueue = new PriorityQueue<>(K);

求第K小,就要用大根堆,每一次更新后,顶点都是整棵树中数值最大的节点,它的孩子都比他小,那么就第K小,就构建一个只有k个节点的堆,最后返回堆顶。

    Queue<Integer> maxQueue= new PriorityQueue<>((v1, v2) -> v2 - v1);

加入:offer()
弹出:poll()
大小:size()

队列

peek() :返回栈顶元素,不在堆栈中删除它。
pop() :返回栈顶元素,并在进程中删除它。
push():在栈顶增加元素

你可能感兴趣的:(搞定算法系列,算法,java,数据结构)