leetcode刷题(javaScript)——堆相关场景题总结

堆是什么?堆都能用树表示,并且一般树的实现都是利用链表。平时使用的最多的是二叉堆,它可以用完全二叉树表示,二叉堆易于存储,并且便于索引。在堆的实现时注意:因为是数组,所以父子节点的关系就不需要特殊的结构去维护了,索引之前通过计算就可以得到,省掉了很多麻烦,如果是链表结构,就会复杂很多。

在JavaScript刷题中,堆(Heap)通常用于解决一些需要高效处理优先级的问题,例如找出最大或最小的K个元素、实现优先队列等。堆在刷题中的应用场景包括但不限于以下几个方面:

  1. 找出最大或最小的K个元素:通过维护一个大小为K的最大堆或最小堆,可以快速找出数组中最大或最小的K个元素。

  2. 合并K个有序数组:可以使用堆来合并K个有序数组,实现高效的合并操作。

  3. 实现优先队列:堆可以用于实现优先队列,保证队列中优先级高的元素先出队。

 数组存储二叉堆

        二叉堆是一种完全二叉树,分为最小堆和最大堆两种类型。用数组存储二叉堆, 完全二叉树要求叶子节点从左往右填满,才能开始填充下一层。

  1. 二叉堆:二叉堆是一种完全二叉树,通常使用数组来表示。在最小堆中,父节点的值小于或等于其子节点的值;在最大堆中,父节点的值大于或等于其子节点的值。

  2. 最小堆:在最小堆中,父节点的值小于或等于其子节点的值。根节点是堆中的最小值。

  3. 最大堆:在最大堆中,父节点的值大于或等于其子节点的值。根节点是堆中的最大值。

注:完全二叉树和满二叉树不同,完全二叉树允许叶子节点不铺满。

动画演示创建堆的过程

 

同一组数据最小堆和最大堆是唯一的吗?

同一组数据的最小堆或最大堆不是唯一的。以最小堆为例:最小堆是一种特殊的二叉堆,它满足以下两个性质:

  1. 父节点的值小于或等于其子节点的值。
  2. 堆中任意节点的子树也是一个最小堆。

        由于最小堆只要满足上述性质即可,因此对于同一组数据,可以有多种不同的最小堆表示方式。这是因为在构建最小堆的过程中,可以选择不同的节点作为根节点,从而得到不同的堆结构。

所以,同一组数据的最小堆并不是唯一的,可以有多种不同的表示方式。

如何找到节点i父节点和子节点呢?

        二叉堆在数组是按层次遍历进行存储的,从上至下,从左至右。因此子节点的index要大于父节点的index。在二叉堆中,可以通过以下方式计算父节点和子节点的索引:

  • 父节点索引计算:对于节点i,其父节点的索引为(i-1)/2。
  • 左子节点索引计算:对于节点i,其左子节点的索引为2*i+1。
  • 右子节点索引计算:对于节点i,其右子节点的索引为2*i+2。

如何删除节点?

在删除一个元素之后,整体往前移动是比较费时的,这也是随机存储结构的短板。因此,堆在删除元素的时候是把最后一个叶子节点补充到树根节点。在通过节点移动将堆调整为最小堆或最大堆。

如何通过js构建最大堆

首先构造器得有吧,创建一个空的heap数组;

其次将获取父节点index、左右子节点index、堆大小size、栈顶元素获取、两节点交换这些简单的辅助函数写一下;

主函数insert插入节点:每次从heap的尾部也就是最后一个叶子节点插入,插进去的叶子节点得向上找它的位置吧,写一个up方法,找到插入节点位置;

up方法实现:找到节点的父元素,看父元素是否比自己小,如果小的换就交换,并且递归up方法,直到所有元素都调整好。

删除节点pop方法:这里依然将最后节点移到第一个跟节点,此时根节点一定是最小的,不符合最大堆规则,所以调整跟节点向下移动,写一个down方法移动根节点。这里注意堆的size如果等于1直接删除而不用移动节点。

down方法实现:找到根的左右子节点,选择比根大的节点进行交换,并递归down方法,直到所有节点都调整好。

交换节点swap可以使用数组解构进行交换,而不用单独定义中间值。

class MaxHeap {
    constructor() {
        this.heap = [];
    }
    //获取父元素下标
    getParentIndex(index) {
        return Math.floor((index - 1) / 2);
    }
    //获取左子树下标
    getLeftIndex(index) {
        return 2 * index + 1;
    }
    //获取右子树下标
    getRightIndex(index) {
        return 2 * index + 2;
    }
    //获取堆大小
    size() {
        return this.heap.length;
    }
    //获取堆顶元素
    peek() {
        return this.heap[0];
    }
    //交换节点
    swap(id1, id2) {
        [this.heap[id2], this.heap[id1]] = [this.heap[id1], this.heap[id2]];
    }
    //插入节点
    insert(value) {
        this.heap.push(value);
        this.up(this.size() - 1);
    }
    //删除节点
    pop() {
        let last = this.heap.pop();
        if (this.size() === 0) return;
        this.heap[0] = last;
        this.down(0);
    }
    //向上移动节点
    up(index) {
        const parentIndex = this.getParentIndex(index);
        if (this.heap[index] > this.heap[parentIndex]) {
            this.swap(parentIndex, index);
            this.up(parentIndex);
        }
    }
    //向下移动节点
    down(index) {
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if (
            leftIndex < this.size() &&
            this.heap[leftIndex] > this.heap[index]
        ) {
            this.swap(leftIndex, index);
            this.down(leftIndex);
        }
        if (
            rightIndex < this.size() &&
            this.heap[rightIndex] > this.heap[index]
        ) {
            this.swap(rightIndex, index);
            this.down(rightIndex);
        }
    }
}

如何通过js构建最小堆

与创建最小堆类似。创建堆,往堆里新增元素,删除堆顶,获取堆的父节点下标,获取堆左右子节点下标

 代码示例

class MinHeap {
    constructor() {
        this.heap = [];
    }
    getParentIndex(index) {
        return Math.floor((index - 1) / 2);
    }
    getLeftIndex(index) {
        return index * 2 + 1;
    }
    getRightIndex(index) {
        return index * 2 + 2;
    }
    swap(i1, i2) {
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    //往堆最后添加节点,触发元素上移
    //当前元素与其跟节点进行比较如果大于其跟节点与根节点进行交换,重复操作
    up(index) {
        //如果是0就不移动
        if (index == 0) return;
        //获取父元素
        const parentIndex = this.getParentIndex(index);

        if (this.heap[parentIndex] > this.heap[index]) {
            this.swap(parentIndex, index);
            //对交换后parentIndex继续向上递归
            this.up(parentIndex);
        }
    }
    //从堆顶删除元素时,将子节点移到堆顶,触发元素下移
    down(index) {
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        //左子树小于根节点,交换左子树与根
        if (this.heap[leftIndex] < this.heap[index]) {
            this.swap(leftIndex, index);
            this.down(leftIndex);
        }
    //同理,右子树小于根节点,交换右子树与根
        if (this.heap[rightIndex] < this.heap[index]) {
            this.swap(rightIndex, index);
            this.down(rightIndex);
        }
    }
    //往堆里增加元素
    insert(value) {
        this.heap.push(value);
        this.up(this.heap.length - 1);
    }
    //将堆顶元素弹出
    pop() {
        this.heap[0] = this.heap.pop();
        this.down(0);
    }
    //获取堆顶
    peek() {
        return this.heap[0];
    }
    //获取堆大小
    size() {
        return this.heap.length;
    }
}

 215. 数组中的第K个最大元素

解决数组中的第K个最大元素问题时,通常使用最小堆来实现。通过维护一个大小为K的最小堆,可以在O(NlogK)的时间复杂度内找到数组中的第K个最大元素。

但是题目最近新增了一个要求:就是必须让算法的时间复杂度控制在O(n),使用堆超出了时间复杂度,因此这里仅供参考。具体实现这个题目还是得用快排。

使用最小堆获取数组中第K个最大元素的思路:

  1. 初始化一个大小为K的最小堆:将数组中的前K个元素放入最小堆中。

  2. 遍历数组剩余元素:从第K+1个元素开始遍历数组,对于每个元素,如果大于最小堆的堆顶元素(堆中最小的元素),则将该元素加入最小堆,并移除堆顶元素。

  3. 返回堆顶元素:遍历完成后,最小堆的堆顶元素即为数组中的第K个最大元素。

使用最小堆的优势在于可以保持堆的大小为K,只需维护K个元素,避免了对整个数组进行排序或维护大堆的复杂度。

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function (nums, k) {
    let minHeap = new MinHeap();
    nums.forEach(item => {
        minHeap.insert(item);
        if (minHeap.size() > k) {
            //每次pop的是栈顶,及k+1个元素中最小的
            minHeap.pop();
        }
    })
    return minHeap.peek();
};

class MinHeap {
    constructor() {
        this.heap = [];
    }
    getParentIndex(index) {
        return Math.floor((index - 1) / 2);
    }
    getLeftIndex(index) {
        return index * 2 + 1;
    }
    getRightIndex(index) {
        return index * 2 + 2;
    }
    swap(i1, i2) {
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    //往堆最后添加节点,触发元素上移
    //当前元素与其跟节点进行比较如果大于其跟节点与根节点进行交换,重复操作
    up(index) {
        //如果是0就不移动
        if (index == 0) return;
        //获取父元素
        const parentIndex = this.getParentIndex(index);

        if (this.heap[parentIndex] > this.heap[index]) {
            this.swap(parentIndex, index);
            //对交换后parentIndex继续向上递归
            this.up(parentIndex);
        }
    }
    //从堆顶删除元素时,将子节点移到堆顶,触发元素下移
    down(index) {
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        //左子树小于根节点,交换左子树与根
        if (this.heap[leftIndex] < this.heap[index]) {
            this.swap(leftIndex, index);
            this.down(leftIndex);
        }
        if (this.heap[rightIndex] < this.heap[index]) {
            //同理,右子树小于根节点,交换右子树与根
            this.swap(rightIndex, index);
            this.down(rightIndex);
        }
    }
    //往堆里增加元素
    insert(value) {
        this.heap.push(value);
        this.up(this.heap.length - 1);
    }
    //将堆顶元素弹出
    pop() {
        this.heap[0] = this.heap.pop();
        this.down(0);
    }
    //获取堆顶
    peek() {
        return this.heap[0];
    }
    //获取堆大小
    size() {
        return this.heap.length;
    }
}

 算法可以通过37/41个测试用例,这里仅提供一个最小堆解决的思路

 703. 数据流中的第 K 大元素

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

  • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
  • int add(int val)val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

 思路:也是用最小堆来实现,最小堆前面已经介绍过堆的插入删除,直接将创建堆对象的类拿来用即可。

动态构建一个长度为k的最小堆,对顶即第k大的的元素。因为比k小的元素都被pop出去了

/**
 * @param {number} k
 * @param {number[]} nums
 */
var KthLargest = function (k, nums) {
    //构建一个k个长度的最小堆
    this.k = k;
    this.minHeap = new MinHeap();

    // 初始化最小堆
    for (let num of nums) {
        this.add(num);
    }
};

/** 
 * @param {number} val
 * @return {number}
 */
KthLargest.prototype.add = function (val) {
    this.minHeap.insert(val);
    while (this.minHeap.size() > this.k) {
        this.minHeap.pop();
    }
    return this.minHeap.peek();
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * var obj = new KthLargest(k, nums)
 * var param_1 = obj.add(val)
 */
class MinHeap {
    constructor() {
        this.heap = [];
    }
    getParentIndex(index) {
        return Math.floor((index-1)/2);
    }
    getLeftIndex(index) {
        return index * 2 + 1;
    }
    getRightIndex(index) {
        return index * 2 + 2;
    }
    swap(id1, id2) {
        const temp = this.heap[id1];
        this.heap[id1] = this.heap[id2];
        this.heap[id2] = temp;
    }
    up(index) {
        const parentIndex = this.getParentIndex(index);
        if (this.heap[index] < this.heap[parentIndex]) {
            this.swap(parentIndex, index);
            this.up(parentIndex);
        }
    }

    down(index) {
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);

        if (leftIndex < this.heap.length && this.heap[leftIndex] < this.heap[index]) {
            this.swap(leftIndex, index);
            this.down(leftIndex);

        }
        if (rightIndex < this.heap.length && this.heap[rightIndex] < this.heap[index]) {
            this.swap(rightIndex, index);
            this.down(rightIndex);
        }

    }

    insert(item) {
        this.heap.push(item);
        this.up(this.heap.length - 1);
    }
    pop() {
        if (this.size() == 0) return null;
        this.heap[0] = this.heap.pop();
        this.down(0);
    }
    peek() {
        if (this.size() == 0) return null;
        return this.heap[0];
    }
    size() {
        return this.heap.length;
    }
}

 1046. 最后一块石头的重量

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0

 思想:构建一个最大堆,从堆顶取两个值分别为y,x判断如果y>x则将y-x放入堆中并将堆调整为最大堆。递归直到堆长度小于2。如果堆里还有值则返回堆顶元素,否则为0

/**
 * @param {number[]} stones
 * @return {number}
 */
var lastStoneWeight = function (stones) {
    if (stones.length == 1) return stones[0];
    let maxHeap = new MaxHeap();
    stones.forEach((item) => maxHeap.insert(item));
    while (maxHeap.size() > 1) {
        let y = maxHeap.peek();
        maxHeap.pop();
        let x = maxHeap.peek();
        maxHeap.pop();
        if (y > x) {
            maxHeap.insert(y - x);
        }
    }
    return maxHeap.size() ? maxHeap.peek() : 0;
};
class MaxHeap {
    constructor() {
        this.heap = [];
    }
    getParentIndex(index) {
        return Math.floor((index - 1) / 2);
    }
    getLeftIndex(index) {
        return 2 * index + 1;
    }
    getRightIndex(index) {
        return 2 * index + 2;
    }
    size() {
        return this.heap.length;
    }
    peek() {
        return this.heap[0];
    }
    swap(id1, id2) {
        [this.heap[id2], this.heap[id1]] = [this.heap[id1], this.heap[id2]];
    }
    insert(value) {
        this.heap.push(value);
        this.up(this.size() - 1);
    }
    pop() {
        let last = this.heap.pop();
        if (this.size() === 0) return;
        this.heap[0] = last;
        this.down(0);
    }
    up(index) {
        const parentIndex = this.getParentIndex(index);
        if (this.heap[index] > this.heap[parentIndex]) {
            this.swap(parentIndex, index);
            this.up(parentIndex);
        }
    }
    down(index) {
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if (
            leftIndex < this.size() &&
            this.heap[leftIndex] > this.heap[index]
        ) {
            this.swap(leftIndex, index);
            this.down(leftIndex);
        }
        if (
            rightIndex < this.size() &&
            this.heap[rightIndex] > this.heap[index]
        ) {
            this.swap(rightIndex, index);
            this.down(rightIndex);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/455003.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【智能算法】蝠鲼觅食优化算法(MRFO)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.代码实现4.参考文献 1.背景 2017年&#xff0c;Zhao等人受到蝠鲼自然捕食行为启发&#xff0c;提出了蝠鲼觅食优化算法(Manta Ray Foraging Optimization&#xff0c;MRFO)。 2.算法原理 2.1算法思想 MRFO模拟了蝠鲼在海洋中…

54、WEB攻防——通用漏洞跨域CORS资源JSONP回调域名接管劫持

文章目录 同源策略CORSJSONP跨域回调子域名劫持 同源策略 同源策略包括三个条件&#xff1a;同域名、同域名、同端口。同源策略限制从一个源加载的文档或脚本与来自另一个源的资源进行交互。 CORS CORS&#xff08;跨域资源共享&#xff09;已被所有浏览器支持&#xff0c;跨…

简单了解 vim 编辑器最基础的操作

简单了解 vim 编辑器最基础的操作 vim 这个是 Linux 上自带的一个文本编辑器&#xff0c;使用 vim 就可以更灵活的对文件进行编辑了&#xff08;虽然和记事本的定位差不多,实际上vim的使用要复杂很多&#xff09; 1.打开文件 语法&#xff1a;vim 文件名 示例&#xff1a;…

简单理解NAT模式和桥接模式

目录 桥接模式NAT模式总结 桥接模式 1.桥接模式下 当物理机X创建了一台或多台虚拟机 那么这些创建出来的虚拟机 可以视作一台独立的新机器 加入了该局域网 并允许和该局域网的物理机或者其他虚拟机直接通信 2.问题一在于 C类网的分配是有范围的(0-255) 假如是一个教室里的局域…

智慧公厕建设,助力打造宜居、韧性、可持续的智慧城市

公共厕所作为智慧城市的重要组成部分&#xff0c;对于城市的高质量发展起着至关重要的作用。智慧公厕建设旨在通过全面监测、控制和管理公共厕所&#xff0c;实现多方面功能&#xff0c;包括公共厕所环境监测与调控、厕位占用监测与引导、消耗品监测与缺失提示、安全防范与管理…

WPF监控平台(科技大屏)[一]

跟着B站的视频敲了一个略微复杂的WPF界面,链接如下.在这里我详细的写一份博客进行设计总结. 系统介绍和配置及主窗口设计_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Wy421Y7QD?p1&vd_source4796b18a2e4c1ec8a310391a5644b6da 成果展示 实现过程 总体来说,我的…

Shell常用脚本:hadoop集群启动、停止、重启脚本

脚本内容以我搭建的hadoop集群为例&#xff0c;你们自用的时候自行根据你们的情况进行修改即可 hadoop-cluster-manager.sh #!/bin/bash # 1. 调用此脚本前&#xff0c;请使用ssh-keygen -t rsa、ssh-copy-id -f 目标机器这两个命令使得目标机器是免密登录的 # 2. ssh远程执行…

Linux常用操作命令和服务器硬件基础知识

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

嵌入式学习day37 数据结构

1.sqlite3_open int sqlite3_open( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb /* OUT: SQLite db handle */ ); 功能: 打开数据库文件(创建一个数据库连接) 参数: filename:数据库文…

【智能硬件、大模型、LLM 智能音箱】MBO:基于树莓派、ChatGPT 的桌面机器人

MAKER:David Packman/译:趣无尽(转载请注明出处) 这是国外 Maker David Packman 制作的基于树莓派机器人 MBO,该机器人的外观设计灵感来自动漫 Adventure Time 中的机器人 MBO。它具有强大的交互功能,可实现脱机唤醒词检测、调用 ChatGPT 3.5 进行聊天、机器视觉对图像进…

【开源】SpringBoot框架开发班级考勤管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统基础支持模块2.2 班级学生教师支持模块2.3 考勤签到管理2.4 学生请假管理 三、系统设计3.1 功能设计3.1.1 系统基础支持模块3.1.2 班级学生教师档案模块3.1.3 考勤签到管理模块3.1.4 学生请假管理模块 3.2 数据库设…

实战 | 基于YOLOv9和OpenCV实现车辆跟踪计数(步骤 + 源码)

导 读 本文主要介绍使用YOLOv9和OpenCV实现车辆跟踪计数&#xff08;步骤 源码&#xff09;。 实现步骤 监控摄像头可以有效地用于各种场景下的车辆计数和交通流量统计。先进的计算机视觉技术&#xff08;例如对象检测和跟踪&#xff09;可应用于监控录像&#xff0c;…

2023 收入最高的十大编程语言

本期共享的是 —— 地球上目前已知超过 200 种可用的编程语言&#xff0c;了解哪些语言在 2023 为开发者提供更高的薪水至关重要。 过去一年里&#xff0c;我分析了来自地球各地超过 1000 万个开发职位空缺&#xff0c;辅助我们了解市场&#xff0c;以及人气最高和收入最高的语…

C++_智能指针

目录 1、内存泄漏 1.1 什么是内存泄漏 1.2 内存泄漏的危害 1.3 如何避免内存泄漏 2、智能指针的应用场景 3、智能指针的原理 3.1 RAII 3.2 智能指针的使用 4、智能指针的拷贝问题 5、auto_ptr 6、unique_ptr 7、share_ptr 7.1 循环引用 7.2 weak_ptr 结…

Linux系统运维命令:查看系统的平均负载(查看CPU的负载)

目 录 一、要求 二、快速了解系统资源利用情况的Linux命令 &#xff08;一&#xff09;cat /proc/loadavg命令 1、命令介绍 2、命令输出 3、命令解释 &#xff08;1&#xff09;前三个数字&#xff1a; &#xff08;2&#xff09;第四个值&#xff1a; &…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:TextClock)

TextClock组件通过文本将当前系统时间显示在设备上。支持不同时区的时间显示&#xff0c;最高精度到秒级。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 无 接口 TextClock(options?…

c++进阶(c++里的继承)

文章目录 1.继承的概念及定义1.1继承的概念1.2继承的定义1.2.1定义格式1.2.2继承关系和访问限定符1.2.3继承类成员访问方式的变化 2.基类和派生类对象赋值转化3.继承中的作用域4.派生类的默认成员函数5.继承与友元6.继承域静态成员 1.继承的概念及定义 1.1继承的概念 继承机制…

23、设计模式之访问者模式(Visitor)

一、什么是访问者模式 访问者模式是一种行为型设计模式&#xff0c;它可以用于在不修改已有对象结构的情况下&#xff0c;定义新的操作方式。简单地说就是在不改变数据结构的前提下&#xff0c;通过在数据结构中加入一个新的角色——访问者&#xff0c;来达到执行不同操作的目的…

防御安全(IPSec实验)

目录 需求&#xff1a; pc1 ping通 pc2 ,使用IPSec VPN 拓扑图&#xff1a; ​编辑实验配置&#xff1a; 注意&#xff1a; 直接在路由器r1和r2分别配置即可&#xff0c;路由器r1和r2要写一条缺省指向ISP 实验配置截图如下&#xff1a; 2. r1​编辑 3. r3​编辑 3.r…

【C++】—— 代理模式

目录 &#xff08;一&#xff09;什么是代理模式 &#xff08;二&#xff09;为什么使用代理模式 &#xff08;三&#xff09;代理模式实现步奏 &#xff08;四&#xff09;代码示例 &#xff08;五&#xff09;代理模式优缺点 &#xff08;一&#xff09;什么是代理模式 …