剑指Offer算法题(二)栈、队列、堆

目录

[9. 用两个栈实现队列](#9. 用两个栈实现队列)

[30. 包含 min 函数的栈](#30. 包含 min 函数的栈)

[31. 栈的压入、弹出序列](#31. 栈的压入、弹出序列)

[40. 最小的 K 个数](#40. 最小的 K 个数)

[41.1 数据流中的中位数](#41.1 数据流中的中位数)

[41.2 字符流中第一个不重复的字符](#41.2 字符流中第一个不重复的字符)

[59. 滑动窗口的最大值](#59. 滑动窗口的最大值)


9. 用两个栈实现队列

java 复制代码
package stack_queue_heap;

import java.util.Stack;

// ====================== 核心思路 ======================
// 题目:用两个栈实现队列
// 队列:先进先出 FIFO
// 栈:先进后出 FILO
// 解法:
// 1. stack1 只负责入队(push)
// 2. stack2 只负责出队(pop)
// 3. 当 stack2 为空时,一次性把 stack1 所有元素倒入 stack2
// 这样 stack2 弹出顺序就变成了队列顺序

// 时间复杂度:均摊 O(1)
// 空间复杂度:O(n)
// ======================================================

public class QueueByTwoStack {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

    public void push(int node) {
        stack1.push(node);
    }

    public int pop() {
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                Integer node = stack1.pop();
                stack2.push(node);
            }
        }
        return stack2.pop();
    }

    public static void main(String[] args) {
        QueueByTwoStack test = new QueueByTwoStack();
        test.push(2);
        System.out.println(test.pop());
        test.push(1);
        System.out.println(test.pop());
    }
}

30. 包含 min 函数的栈

java 复制代码
package stack_queue_heap;

import java.util.Stack;

// ====================== 核心思路 ======================
// 题目:实现包含 min 函数的栈,要求 push、pop、top、min 都是 O(1)
// 解法:用两个栈
// 1. stack:正常存储所有元素
// 2. smallStack:同步存储【当前栈的最小值】
// 每压入一个数,最小栈就压入当前最小;弹出时两个栈一起弹

// 时间复杂度:O(1),所有操作都是常数时间
// 空间复杂度:O(n),需要一个辅助栈
// ======================================================

public class MinStack {
    Stack<Integer> stack = new Stack<>();
    Stack<Integer> smallStack = new Stack<>();

    public void push(int node) {
        stack.push(node);
        if (smallStack.isEmpty()) {
            smallStack.push(node);
        } else {
            Integer peek = smallStack.peek();
            if (peek < node) {
                smallStack.push(peek);
            } else {
                smallStack.push(node);
            }
        }
    }

    public void pop() {
        stack.pop();
        smallStack.pop();
    }

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

    public int min() {
        return smallStack.peek();
    }

    public static void main(String[] args) {
        MinStack test = new MinStack();
        test.push(-1);
        test.push(2);
        System.out.println(test.min());
        System.out.println(test.top());
        test.pop();
        test.push(1);
        System.out.println(test.top());
        System.out.println(test.min());

    }
}

31. 栈的压入、弹出序列

java 复制代码
package stack_queue_heap;

import java.util.Stack;

// ====================== 核心思路 ======================
// 题目:判断第二个序列是否是第一个序列的合法弹出顺序
// 解法:用一个栈模拟压入和弹出操作
// 1. 按入栈顺序,逐个将元素压入辅助栈
// 2. 每压入一个元素,就检查栈顶是否等于当前待弹出的元素
// 3. 如果相等,就弹出栈顶,并将待弹出指针后移一位,继续检查
// 4. 当所有元素都压入后,如果辅助栈为空,说明是合法弹出序列

// 时间复杂度:O(n),每个元素入栈、出栈各一次
// 空间复杂度:O(n),需要一个辅助栈存储元素
// ======================================================

public class IsPopOrder {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * @param pushV int整型一维数组
     * @param popV  int整型一维数组
     * @return bool布尔型
     */
    public boolean IsPopOrder(int[] pushV, int[] popV) {
        // write code here
        Stack<Integer> stack = new Stack<>();
        int length = pushV.length;
        for (int pushIndex = 0, popIndex = 0; pushIndex < length; pushIndex++) {
            stack.push(pushV[pushIndex]);
            while (!stack.isEmpty() && stack.peek() == popV[popIndex]) {
                stack.pop();
                popIndex++;
            }
        }
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        IsPopOrder test = new IsPopOrder();
        int[] pushV = new int[]{1, 2, 3, 4, 5};
        int[] popV = new int[]{4, 3, 5, 2, 1};
        System.out.println(test.IsPopOrder(pushV, popV));
    }
}

40. 最小的 K 个数

java 复制代码
package stack_queue_heap;

import java.util.ArrayList;
import java.util.PriorityQueue;

// ====================== 核心思路 ======================
// 题目要求:找出数组中不去重的最小 k 个数
// 解法:使用【最大堆】维护当前最小的 k 个元素
// 1. 维护一个大小为 k 的最大堆,堆顶始终是堆内最大值
// 2. 遍历数组,每个元素都加入堆
// 3. 当堆大小超过 k 时,弹出堆顶(淘汰最大的,保留小的)
// 4. 遍历结束后,堆中剩下的就是最小 k 个数

// 时间复杂度:O(n log k),每个元素入堆出堆耗时 log k
// 空间复杂度:O(k),堆只保存 k 个元素
// ======================================================

public class KSmallestWithDuplicates {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * @param input int整型一维数组
     * @param k     int整型
     * @return int整型ArrayList
     */
    public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        // write code here
        if (input == null || input.length == 0 || input.length < k) {
            return new ArrayList<>();
        }
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);//默认小顶堆
        for (int i : input) {
            maxHeap.add(i);
            if (maxHeap.size() == k + 1) {
                maxHeap.poll();
            }
        }
        return new ArrayList<>(maxHeap);
    }

    public static void main(String[] args) {
        int[] input = new int[]{4, 5, 1, 6, 2, 7, 3, 8};
        int k = 4;
        KSmallestWithDuplicates test = new KSmallestWithDuplicates();
        System.out.println(test.GetLeastNumbers_Solution(input, k));
    }
}

41.1 数据流中的中位数

java 复制代码
package stack_queue_heap;

import java.util.PriorityQueue;

// ====================== 核心思路 ======================
// 题目:不停插入数据流,随时获取中位数
// 中位数:奇数个取中间,偶数个取中间两数平均值
// 解法:用两个堆维护数据流
// 1. leftMaxHeap:大顶堆,存放较小的一半数,堆顶 = 左边最大
// 2. rightMinHeap:小顶堆,存放较大的一半数,堆顶 = 右边最小
// 3. 保证:左边数量 = 右边数量 或 左边 = 右边 + 1
// 4. 奇数个:中位数 = 左边堆顶
//    偶数个:中位数 = (左堆顶 + 右堆顶) / 2

// 时间复杂度:Insert O(log n),GetMedian O(1)
// 空间复杂度:O(n)
// ======================================================

public class FindMedianFromDataFlow {
    private PriorityQueue<Integer> leftMaxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
    private PriorityQueue<Integer> rightMinHeap = new PriorityQueue<>();
    private int N = 0;

    public void Insert(Integer num) {
        N++;
        if (N % 2 == 0) {
            leftMaxHeap.add(num);
            rightMinHeap.add(leftMaxHeap.poll());
        } else {
            rightMinHeap.add(num);
            leftMaxHeap.add(rightMinHeap.poll());
        }
    }

    public Double GetMedian() {
        if (N % 2 == 0) {
            return (leftMaxHeap.peek() + rightMinHeap.peek()) / 2.0;
        } else {
            return (double) leftMaxHeap.peek();
        }
    }

    public static void main(String[] args) {
        FindMedianFromDataFlow test = new FindMedianFromDataFlow();
        test.Insert(5);
        System.out.println(test.GetMedian());
        test.Insert(2);
        System.out.println(test.GetMedian());
        test.Insert(3);
        System.out.println(test.GetMedian());
        test.Insert(4);
        System.out.println(test.GetMedian());
        test.Insert(1);
        System.out.println(test.GetMedian());
        test.Insert(6);
        System.out.println(test.GetMedian());
        test.Insert(7);
        System.out.println(test.GetMedian());
        test.Insert(0);
        System.out.println(test.GetMedian());
        test.Insert(8);
        System.out.println(test.GetMedian());
    }
}

41.2 字符流中第一个不重复的字符

java 复制代码
package stack_queue_heap;

import java.util.LinkedList;
import java.util.Queue;

// ====================== 核心思路 ======================
// 题目:字符流中第一个不重复的字符
// 要求:Insert插入字符,FirstAppearingOnce返回第一个只出现一次的字符
// 解法:计数数组 + 队列
// 1. times[128]:记录每个ASCII字符出现的次数
// 2. 队列:按插入顺序保存字符,保证队首始终是【第一个不重复的字符】
// 3. 每次插入后清理队首:重复出现的字符直接弹出,直到队首只出现一次
// 4. 无满足条件字符时返回 '#'

// 时间复杂度:O(1) Insert,O(1) 获取结果
// 空间复杂度:O(n)
// ======================================================

public class FirstUniqueCharInStream {
    private int[] times = new int[128];
    private Queue<Character> queue = new LinkedList<>();

    //Insert one char from stringstream
    public void Insert(char ch) {
        times[ch]++;
        queue.add(ch);
        while (!queue.isEmpty() && times[queue.peek()] > 1) {
            queue.poll();
        }
    }

    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce() {
        return queue.isEmpty() ? '#' : queue.peek();
    }

    public static void main(String[] args) {
        FirstUniqueCharInStream test = new FirstUniqueCharInStream();
        test.Insert('g');
        System.out.println(test.FirstAppearingOnce());
        test.Insert('o');
        System.out.println(test.FirstAppearingOnce());
        test.Insert('o');
        System.out.println(test.FirstAppearingOnce());
        test.Insert('g');
        System.out.println(test.FirstAppearingOnce());
        test.Insert('l');
        System.out.println(test.FirstAppearingOnce());
        test.Insert('e');
        System.out.println(test.FirstAppearingOnce());
    }
}

59. 滑动窗口的最大值

java 复制代码
package stack_queue_heap;

import java.util.ArrayList;
import java.util.PriorityQueue;

// ====================== 核心思路 ======================
// 题目:求滑动窗口中的最大值
// 解法:利用最大堆(大顶堆)动态维护窗口内的最大值
// 1. 先将第一个窗口的元素加入最大堆,堆顶即为当前最大值
// 2. 窗口向右滑动,移除左端离开窗口的元素,加入右端新进入的元素
// 3. 每次取堆顶作为当前窗口最大值,加入结果集

// 时间复杂度:O(n*k) 堆移除元素需要遍历查找,效率较低
// 空间复杂度:O(k) 堆最多存储 k 个元素
// ======================================================

public class MaxSlidingWindow {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * @param num  int整型一维数组
     * @param size int整型
     * @return int整型ArrayList
     */
    public ArrayList<Integer> maxInWindows(int[] num, int size) {
        // write code here
        if (num.length == 0 || size <= 0 || size > num.length) {
            return new ArrayList<>();
        }
        int n = num.length;
        ArrayList<Integer> res = new ArrayList<>();
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(((o1, o2) -> o2 - o1));
        for (int i = 0; i < size; i++) {
            maxHeap.add(num[i]);
        }
        res.add(maxHeap.peek());
        for (int i = 0, j = i + size; j < n; i++, j++) {
            maxHeap.remove(num[i]);
            maxHeap.add(num[j]);
            res.add(maxHeap.peek());
        }
        return res;
    }

    public static void main(String[] args) {
        int[] num = new int[]{2, 3, 4, 2, 6, 2, 5, 1};
        int size = 3;
        MaxSlidingWindow test = new MaxSlidingWindow();
        System.out.println(test.maxInWindows(num, size));
    }
}
相关推荐
李宥小哥3 小时前
SQLite04-表数据管理
java·jvm·数据库
李宥小哥3 小时前
SQLite05-常用函数
java·开发语言·jvm
huohuopro3 小时前
idea配置servlet项目
java·servlet·intellij-idea
皮卡狮3 小时前
C++面向对象编程的三大核心特性之一:多态
开发语言·c++
zzb15803 小时前
Agent学习-ReAct框架
java·人工智能·python·机器学习·ai
zhangx1234_3 小时前
java list介绍
java·开发语言·list
Java面试题总结3 小时前
Go运行时系统解析: runtime包深度指南
开发语言·后端·golang
识君啊3 小时前
拆分与合并的艺术·分治思想:Java归并排序深度解析
java·数据结构·算法·排序算法·归并排序·分治
左左右右左右摇晃3 小时前
Java Object 类笔记
java·笔记