堆/栈/队列的相关算法

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

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

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

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

堆/栈/队列的相关算法

可以解决的经典问题

  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<Integer> 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()在栈顶增加元素

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6