当前位置: 首页 > article >正文

2024收尾工作

目录

开场白

栈与队列

LeetCode232. 用栈实现队列

LeetCode225. 用队列实现栈

LeetCode102. 二叉树的层序遍历

LeetCode103. 二叉树的锯齿形层序遍历

堆(优先级队列)

堆排序

LeetCode215. 数组中的第 k 个最大元素

总结


开场白

今天是除夕,在此首先祝各位读者朋友们新年快乐!愿你在新的一年里,心怀希望,勇敢追梦,愿每一天都充满温暖与惊喜,愿所有的努力都能开花结果,所有的期待都能如愿以偿。

从寒假开始,为了准备面试,我在我的个人CSDN账号陆续发布了Java实现数据结构与算法的相关文章,意在从我个人角度深度理解数据结构。将相关文章分享分布后,很多读者通过私信方式与我交流讨论,每次看到大家的留言,无论是鼓励、建议,还是探讨问题,都让我倍感温暖,也让我更加坚定了继续分享知识的决心。各位读者的支持是我持续创作的最大动力。

言归正传,我们今天简要分析前几篇文章中涉及到的数据结构在 LeetCode 中的应用。很多数据结构在实际问题中是结合着使用的,例如二叉树的层序遍历需要使用到队列作为辅助,因为层序遍历实际上是一种广度优先搜索(Breadth First Search,BFS,二叉树的前中后序遍历的迭代方式的实现需要使用到栈,即使在递归实现中我们使用三行代码可以实现需求,但是递归函数底层使用了函数调用栈,如果使用迭代方式,我们就需要使用栈来模拟函数调用栈的行为。

栈与队列

LeetCode232. 用栈实现队列

232. 用栈实现队列 - 力扣(LeetCode)

本题要求使用两个栈来模拟一个队列,此队列应该支持常见的入队、出队、获取队首元素以及判空几个常见操作。

这个题让我的思绪回到了大一考学院实验室的时候,当时过了笔试环节,面试环节一个学长很突然的问了我这个问题——“如何用两个栈模拟一个队列”,当时一紧张,说成了两栈共享空间,学长说 “不好意思,我没太明白你的意思”。

实际上只要了解了队列和栈的特性,解决这个问题就很容易了。我们需要定义两个栈,分别是输入栈输出栈,前者用于接收入队元素,后者用于处理获取队首元素和出队操作。假设我们连续向队列中入队元素,我们直接将这些元素按顺序压入输入栈,此时输入栈就保存了所有入队元素,栈顶元素就是队尾元素,如果我们需要执行出队获取队首元素操作,我们需要将输入栈的所有元素按顺序出栈,然后重新压入到输出栈,那么此时输出栈的栈顶元素就变成了模拟队列的队首元素,执行出队操作就是将输出栈的栈顶元素出栈,获取队首元素就是获取输出栈的栈顶元素。如果后续还需要入队元素,直接将元素压入输入栈,如果需要获取队首元素或者出队,先判断一下输出栈是否为空,如果输出栈为空,就需要执行相同的操作将输入栈的元素按顺序入栈到输出栈,此时栈顶元素就是队首元素。实现代码如下。

class MyQueue {

    private final ArrayStack<Integer> inputStack;
    private final ArrayStack<Integer> outputStack;
    private int size;

    public MyQueue() {
        inputStack = new ArrayStack<>(101);
        outputStack = new ArrayStack<>(101);
        size = 0;
    }
    
    public void push(int x) {
        inputStack.push(x);
        size++;
    }
    
    public int pop() {
        if (outputStack.isEmpty())
            while (!inputStack.isEmpty())
                outputStack.push(inputStack.pop());
        size--;
        return outputStack.pop();
    }
    
    public int peek() {
        if (outputStack.isEmpty())
            while (!inputStack.isEmpty())
                outputStack.push(inputStack.pop());
        return outputStack.peek();
    }
    
    public boolean empty() {
        return size == 0;
    }
}

在以上实现中,我们在类 MyQueue 中定义了一个用于表示模拟队列存储元素数量的变量 size,通过此方式在后续实现 empty 方法时就很容易,直接判断 size 是否为 0 即可,我们也可以通过判断两栈是否同时为空得出队列是否为空。

需要提前指出的是,我们这里使用了顺序栈,在构造顺序栈时需要传入期望规模参数 capacity,本题已经说明,入队出队操作不会超过 100 次,我们直接规定顺序栈的期望规模为 101,这样就不需要执行判满判空等操作。再次提醒,提交代码时需要将自定义的顺序栈类 ArrayStack 同时提交。当然,我们也可以使用 java.util.LinkedList 类来实现栈。

LeetCode225. 用队列实现栈

225. 用队列实现栈 - 力扣(LeetCode)

本题需要使用两个队列模拟栈的操作,模拟栈需要支持普通栈的入栈、出栈、获取栈顶元素和判空操作。我们知道,队列是一种先入先出FIFO数据结构,而栈是一种后入先出LIFO数据结构,如果按照顺序将元素入队,那么队首一定是模拟栈的栈底元素,而队尾一定是模拟栈的栈顶元素。所以使用队列模拟栈的时候,我们可以直接让元素序列入队到第一个队列,在出栈或获取栈顶元素时,让队尾之前的所有元素出队,并让这些元素按刚才出队的顺序入队到另一个队列,这个队列的作用就是暂存模拟栈栈顶元素的所有后继元素,如果是出栈操作,将第一个队列剩余的最后一个元素出队并用变量接收返回即可,然后将第二个队列的所有元素出队,让这些元素按照顺序重新入队到第一个队列;如果只是单纯获取栈顶元素,我们在拿到第一个队列剩余的最后一个元素后,第二个队列的所有元素出队,让这些元素按照出队顺序重新入队到第一个队列。方便起见,我们可以自定义一个用于表示模拟栈中存储元素数量的变量 size,所以实现判空操作就很简单了,直接判断 size 是否为 0 即可。代码如下。

class MyStack {

    private final ArrayQueue<Integer> queue1;
    private final ArrayQueue<Integer> queue2;
    private int size;

    public MyStack() {
        queue1 = new ArrayQueue<>(101);
        queue2 = new ArrayQueue<>(101);
        size = 0;
    }

    public void push(int x) {
        queue1.offer(x);
        size++;
    }

    public int pop() {
        int temp = size;
        while (temp -- != 1)
            queue2.offer(queue1.pull());
        Integer ans = queue1.pull();
        while (!queue2.isEmpty())
            queue1.offer(queue2.pull());
        size--;
        return ans;
    }

    public int top() {
        int temp = size;
        while (temp -- != 1)
            queue2.offer(queue1.pull());
        Integer ans = queue1.pull();
        while (!queue2.isEmpty())
            queue1.offer(queue2.pull());
        queue1.offer(ans);
        return ans;
    }

    public boolean empty() {
        return size == 0;
    }
}

当然本题的进阶实现是直接使用一个队列模拟栈操作,可以优化的地方就是元素转移阶段,我们可以不需要定义另一个队列暂存这些元素,而是直接让这些元素重新入队即可。整个调整过程就是,将队尾之前的所有元素出队,每个元素出队后又立刻入队到队尾,整个操作结束后,队首元素就是模拟栈的栈顶元素。代码如下。

class MyStack {

    private final ArrayQueue<Integer> queue;
    private int size = 0;

    public MyStack() {
        queue = new ArrayQueue<>(101);
        size = 0;
    }

    public void push(int x) {
        queue.offer(x);
        size++;
    }

    public int pop() {
        int temp = size - 1;
        while (temp -- != 0)
            queue.offer(queue.pull());
        size--;
        return queue.pull();
    }

    public int top() {
        int temp = size - 1;
        while (temp -- != 0)
            queue.offer(queue.pull());
        Integer ans = queue.pull();
        queue.offer(ans);
        return ans;
    }

    public boolean empty() {
        return size == 0;
    }
}

我们通过上面两个代码片段不难发现,出栈和获取栈顶元素这两个操作有重复代码,我们不妨将共有的代码提取到入栈操作处,即在入栈时就进行队列中元素的调整,让队尾元素来到队首,通过这种实现方式,在后续的出栈和获取栈顶元素时,我们直接对队首元素进行操作即可。代码如下。

class MyStack {

    private final ArrayQueue<Integer> queue1;
    private final ArrayQueue<Integer> queue2;
    private int size;

    public MyStack() {
        queue1 = new ArrayQueue<>(101);
        queue2 = new ArrayQueue<>(101);
        size = 0;
    }

    public void push(int x) {
        queue1.offer(x);
        int temp = size;
        while (temp -- != 0)
            queue2.offer(queue1.pull());
        while (!queue2.isEmpty())
            queue1.offer(queue2.pull());
        size++;
    }

    public int pop() {
        size--;
        return queue1.pull();
    }

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

    public boolean empty() {
        return size == 0;
    }
}
class MyStack {

    private final ArrayQueue<Integer> queue;
    private int size = 0;

    public MyStack() {
        queue = new ArrayQueue<>(101);
        size = 0;
    }

    public void push(int x) {
        queue.offer(x);
        int temp = size;
        while (temp -- != 0)
            queue.offer(queue.pull());
        size++;
    }

    public int pop() {
        size--;
        return queue.pull();
    }

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

    public boolean empty() {
        return size == 0;
    }
}

LeetCode102. 二叉树的层序遍历

102. 二叉树的层序遍历 - 力扣(LeetCode)

二叉树的层序遍历遵循着广度优先搜索的不断延伸思想。具体来说,我们需要使用一个队列用来暂存需要遍历的结点,让根节点入队,每次取出队列中的队首结点并将其的左右孩子存入队列,不断进行此操作,就可以让队列中存储着当前层的下一层结点,实际上,队列的出队顺序就是此二叉树的层序遍历序列。

在具体实现中,由于队列只有在遍历到叶子结点后才会为空,我们需要专门定义一个变量用来存储当前层的结点数,每次取出队列元素都是基于这个变量的,否则会取出下一层的结点。

    public List<List<Integer>> levelOrder(TreeNode root) {
        if (root == null)
            return new ArrayList<>();
        List<List<Integer>> ans = new ArrayList<>();
        ArrayQueue<TreeNode> queue = new ArrayQueue<>(2001);
        queue.offer(root);
        int levelNum = 1;
        while (!queue.isEmpty()) {
            List<Integer> list = new ArrayList<>();
            int cnt = 0;
            for (int i = 1; i <= levelNum; i++) {
                TreeNode node = queue.pull();
                list.add(node.val);
                if (node.left != null) {
                    queue.offer(node.left);
                    cnt++;
                }
                if (node.right != null) {
                    queue.offer(node.right);
                    cnt++;
                }
            }
            levelNum = cnt;
            ans.add(list);
        }
        return ans;
    }

LeetCode103. 二叉树的锯齿形层序遍历

103. 二叉树的锯齿形层序遍历 - 力扣(LeetCode)

本题与上题类似,我们认为根节点所处层为第一层,通过题目要求我们不难发现,当层序号为奇数时,我们需要从左到右遍历,反之,如果层序号为偶数时,我们需要从右到左遍历,这样就可以实现锯齿形遍历。我们在每层接收结果的时候需要使用一个双端队列,若当前层的层序号为奇数,我们将队列的队首结点数据从尾部插入到双端队列,否则从头部插入双端队列,后续的操作与上一题是一致的,不再赘述。

为了方便实现,我们使用了 java.util.LinkedList 进行实现,代码如下。

    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        if (root == null)
            return new ArrayList<>();
        List<List<Integer>> ans = new ArrayList<>();
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int levelNum = 1;
        boolean isOdd = true;
        while (!queue.isEmpty()) {
            int cnt = 0;
            LinkedList<Integer> level = new LinkedList<>();
            for (int i = 1; i <= levelNum; i++) {
                TreeNode node = queue.poll();
                if (node != null) {
                    if (isOdd)
                        level.offerLast(node.val);
                    else
                        level.offerFirst(node.val);
                    if (node.left != null) {
                        queue.offer(node.left);
                        cnt++;
                    }
                    if (node.right != null) {
                        queue.offer(node.right);
                        cnt++;
                    }
                }
            }
            levelNum = cnt;
            isOdd = !isOdd;
            ans.add(level);
        }
        return ans;
    }

堆(优先级队列)

堆排序

堆排序是基于堆的一种排序方法,假设我们使用大顶堆进行堆内元素的排序,执行建堆操作后,堆顶元素是所有元素的最大值,我们可以选择将堆顶元素与堆的最后一个元素进行交换,然后让堆的 size 变量减 1,这样就表示我们不再维护最大值元素,此时将交换到堆顶的元素执行一次下潜操作,新的堆顶元素就是剩下的 size-1 个元素中的最大值。不断执行此操作,直到堆中维护的元素数量为 1 为止。

    public static void main(String[] args) {
        MaxHeap heap = new MaxHeap(new int[]{1, 34, 54, 13, 25, 6, 9, 17});
        System.out.println(Arrays.toString(heap.array));
        while (heap.size > 1) {
            heap.swap(0, heap.size - 1);
            heap.size --;
            heap.down(0);
        }
        System.out.println(Arrays.toString(heap.array));
    }

LeetCode215. 数组中的第 k 个最大元素

215. 数组中的第K个最大元素 - 力扣(LeetCode)

本题给定一个数组和一个整型变量 k,届时需要返回数组中第 k 个最大的元素。初见此题,第一想法是直接对数组进行排序,然后返回指定位置元素即可,但是这种方式并没有考虑到数组中存在重复元素的情况。

我们可以直接使用一个大顶堆来维护所有元素,然后执行 k-1 次出队操作即可。

    public int findKthLargest(int[] nums, int k) {
        MaxHeap heap = new MaxHeap(nums);
        while (k -- != 0)
            heap.pull();
        return heap.peek();
    }

实际上,我们也并不需要维护所有的元素,直接定义一个大小为 k 的小顶堆,先将前 k 个元素存入堆中,然后继续遍历数组,若当前遍历的元素大于堆顶元素,则执行一次替换堆顶元素操作,最终堆顶元素就是第 k 个最大元素。

    public int findKthLargest(int[] nums, int k) {
        MinHeap heap = new MinHeap(k);
        for (int i = 0; i < k; i ++)
            heap.offer(nums[i]);
        for (int i = k; i < nums.length; i ++) {
            if (nums[i] > heap.peek())
                heap.replace(nums[i]);
        }
        return heap.peek();
    }

总结

本文写的比较仓促,因为我要过年了。


http://www.kler.cn/a/523163.html

相关文章:

  • 【Jave全栈】Java与JavaScript比较
  • 【Redis】List 类型的介绍和常用命令
  • mysql DDL可重入讨论
  • 省市区三级联动
  • AndroidCompose Navigation导航精通1-基本页面导航与ViewPager
  • 物业巡更系统在现代社区管理中的优势与应用探讨
  • Linux环境基础开发工具的使用(apt, vim, gcc, g++, gbd, make/Makefile)
  • 【C语言----数组详解】
  • Gurobi基础语法之 LinExpr 类
  • AI学习指南Ollama篇-Ollama的多模态应用探索
  • Codeforces Round 276 (Div. 1) B. Maximum Value(数学+二分)【2100】
  • 53. 常见TCP端口号及其功能
  • 基于微信小程序的电子竞技信息交流平台设计与实现(LW+源码+讲解)
  • 【踩坑日常,已解决】彻底修改IDEA项目的JDK版本,8改为17
  • 脚本运行禁止:npm 无法加载文件,因为在此系统上禁止运行脚本
  • LeetCode题练习与总结:标签验证器--591
  • 手写一个深克隆!!
  • LeetCode:70. 爬楼梯
  • 排序算法- H指数
  • 【C语言分支与循环结构详解】
  • 如何下载SQLServer
  • fprintf(‘parametric_vector:\n‘); disp(parametric_vector);
  • 损失函数 Loss Function
  • 【番外篇】鸿蒙扫雷天纪:运混沌灵智勘破雷劫天局
  • 深入探索 HTML5 拖拽效果 API:打造流畅交互体验
  • 27.收益的定义是什么?