10.5二分专练,二分边界情况,+1不加1的判断,方向判断,各种DEBUG

news2024/10/6 0:21:11

5

https://leetcode.cn/problems/minimum-speed-to-arrive-on-time/submissions/570242512/
就是说总时间是
前n-1量汽车的运行时间,向上取整,然后再加上最后一辆列车的运行时间
最快的话是需要n-1个小时
搜索空间就是时速,左边界是1,右边界是其中最大值
当为最大值时,就是n-1再加上最后的距离与该时速的比值
如果大于目标时间hour,说明不满足条件,应当增大速度,即向右侧找,同时舍去该解,left=mid+1
如果小于等于hour,说明满足条件,可以去找左侧的最小值,right=mid
不应该加1
在这里插入图片描述

class Solution {
public:
    int minSpeedOnTime(vector<int>& dist, double hour) {
        int leftB=1;
        int rightB=10e7;
        // for(int i:dist){
        //     if(i>rightB){
        //         rightB=i;
        //     }
        // }
        if(dist.size()>=hour+1){
            return -1;
        }
        int mid=0;
        while(leftB<rightB){
            int tarN=0;
            mid=(leftB+rightB)/2;
            for(int i=0;i<dist.size()-1;i++){
                tarN+=((dist[i]/mid)+((dist[i]%mid)?1:0));
            }
            double tar=double(dist[dist.size()-1])/double(mid);
           // cout<<"预期的小时部分"<<tar<<endl;
            tar+=tarN;
            if(tar>hour){
                // cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"速度为"<<mid<<"总用时是"<<tar<<"前n-1量用时为"<<tarN<<endl;
                leftB=mid+1;
            }else{
                //cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"速度为"<<mid<<"总用时是"<<tar<<"前n-1量用时为"<<tarN<<endl;
                rightB=mid;
            }
        }
        mid=(leftB+rightB)/2;
        return mid;
    }
};

优化

对初始的上界与下界进行优化,下界至少是1的,但是更好情况是路程总和与要求时间的平均,或者说这个就是最好的期望解,即每段都是相同的,
对于上界,没看懂它的优化,先放在这里

l = max(1, int(sum(dist)//hour))
r = int(sum(dist)//(hour-n+1) + 1)

但是要注意,下界在用平均的时候,务必要增加一个与1的Max判断,就是为了防止0出现在搜索空间当中
而且在算累和的时候,务必要注意数据范围,一般应当要用Longlong来记录累和,用Int的话必爆

在用上平均值优化后,优化效果并不是很好\\,甚至还有点拉托了

Debug

限制值错误

第一次是因为忽略了非整数部分的取值,即没有向上取整,即
tarN+=(dist[i]/mid);
改为
tarN+=((dist[i]/mid)+((dist[i]%mid)?1:0));
即可

上界取值错误

在这里插入图片描述
这是因为一开始认为速度右侧最大值是里程里的最大值,但是如果它是在最后的话,完全可以使速度达到一个很大的值,从而使最后一段的速度降下来
这个问题,个人目前没有很好的解决办法
参考官方题解
由于时限最小单位是0.01,最大的距离是 1 0 5 10^5 105,所以在最后一段,极限的最快速度就是 1 0 7 10^7 107,如果再快的话,就会导致时间小于0.01了,所以时间上限就是 1 0 7 10^7 107

小数除法

这次错是因为最后一段的小数部分,由于是让整数之间进行相除,即
double tar=(dist[dist.size()-1])/(mid);
会导致tar虽然是double类型,但根本不会产生小数,只有把分子分母都强转为double,才可以使tar为小数,即
double tar=double(dist[dist.size()-1])/double(mid);

4

https://leetcode.cn/problems/minimized-maximum-of-products-distributed-to-any-store/
就是去搜索每个店被分到的商品最大值x,
左边界是1,右边界是其中的最大值
x越大,那么分出的商店数越少;
限制条件是商店数,如果x对应到的商店数k,
大于给定的商店数n,则说明不满足条件,应当向减少商店数的方向去搜索,即x大、右侧去搜索,left=mid+1
小于等于给定的商店数,说明满足条件,尝试增大商店数,即向左侧去搜索,right=mid,并保留该解
只剩两个值的时候,应首先向左侧去搜索,所以不加1

在这里插入图片描述

一遍过

class Solution {
public:
    int minimizedMaximum(int n, vector<int>& quantities) {
        int leftB=1;
        int rightB=quantities[0];
        for(int i:quantities){
            if(i>rightB){
                rightB=i;
            }
        }
        if(n<=quantities.size()){
            return rightB;
        }
        int mid;
        while(leftB<rightB){
            int tar=0;
            mid=(leftB+rightB)/2;
            for(int i:quantities){
                tar+=((i/mid)+((i%mid)?1:0));
            }
            if(tar>n){
                leftB=mid+1;
            }else{
                rightB=mid;
            }
        }
        mid=(leftB+rightB)/2;
        return mid;
    }
};

3

https://leetcode.cn/problems/minimum-limit-of-balls-in-a-bag/description/

歧途——贪心大顶堆

一开始想法就是维护一个最大堆,每次都取堆顶元素,然后给他除以2,如此循环操作给定的次数,最后堆顶就是代价
之所以要除以2,是因为如果不这样的话,必然会有比2还大的数在里面,相当于没整

考虑到不能整除的问题,每次加入就是加入(num)/2与(num+1)/2,这样就是如果能整除,加的也是两个相同的数;不能整除,就是一个左值,一个右值

class Solution {
public:
    int minimumSize(vector<int>& nums, int maxOperations) {
        priority_queue<int>p;
        for(int i=0;i<nums.size();i++){
            p.push(nums[i]);
        }
        for(int i=1;i<=maxOperations;i++){
            int num=p.top();
            p.pop();
            p.push(num/2);
            p.push((num+1)/2);
        }
        return p.top();
    }
};

问题
在这里插入图片描述
考虑这种情况,9-》4和5,5再拆分成2和3,导致结果是4,但如果一开始分成3和6,第二次拆成3,3,3,就会使最后结果为3,贪心确实行不通

贪心行不通的原因

在这里插入图片描述
参照题解,贪心的思路无非是在处于2y与3y之间的值,使其变为y与2y之间的值,那么要让原值完全处于y之下,则需要3次操作,而如果是变为<=y与y与2Y之间的两个数,则只需要2次操作

也就是逆向思维不去向怎么从原始值变出最优解,而是先假定一个最优解,然后看各个值怎么达到这个最优解
而问题也就出现在上面的贪心思路到这个最优解的路径上了

二分

如果卡死一个最优解的线,每个袋子要达到这个最优解,必然要有一个策略
就是对于含有x个东西的袋子,怎么拆分才能使其产生的袋子都达到y以下
显然也应当逆向思维,每次拆分相当于多了一个袋子,最后不就是把x个东西分到k个袋子当中,每个袋子里装的数量不超过y吗?
这样的话最优解必然也是每个袋子里装的都是y
而按照其它划分方法,尤其是上面的贪心,每次分一半的话,很有可能多用
就是把问题看成,一共有x个东西,每个袋子只能装y个,问一共需要多少个袋子的问题

那么每个袋子能装的最大值是数组里的最大值,最小值是1,
限制条件就是切分的次数,即多用的袋子的数量o
如果袋子容量对应的切分数大于o的话,说明不满足条件,应当要减少切分次数,即扩大每个袋子的容量以减少袋子所用的数量
如果小于等于o,说明满足条件,保留该解,并尝试缩小袋子容量,即向左侧找,right=mid,从而减小开销
对于Mid,由于满足条件时,是尝试向左取值,所以不应当+1;

搜索空间就是袋子所能放的数量

在这里插入图片描述

class Solution {
public:
    int minimumSize(vector<int>& nums, int maxOperations) {
        int leftB=1;
        int rightB=nums[0];
        for(int i:nums){
            if(i>rightB){
                rightB=i;
            }
        }
        int mid=1;
        while(leftB<rightB){
            int sum=0;
            mid=(leftB+rightB)/2;
            for(int i:nums){
                sum+=(i/mid)+((i%mid)?0:-1);
            }
            if(sum>maxOperations){
                cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"袋子装的数量为"<<mid<<"切分数是"<<sum<<endl;
                leftB=mid+1;
            }else{
                cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"袋子装的数量为"<<mid<<"切分数是"<<sum<<endl;
                rightB=mid;
            }
        }
        mid=(leftB+rightB)/2;
        return mid;
    }
};

DEBUG

端点情况

考虑这个切分,就是想象成一个线段,切分成k段不大于Y的子段的次数
如果刚好达到端点的情况,就不应当再继续分
即将 sum+=(i/mid)改为sum+=(i/mid)+((i%mid)?0:-1);

2

https://leetcode.cn/problems/koko-eating-bananas/
最大值肯定是香蕉堆里的最大值
最小值期望是香蕉总数除以小时数
如果堆数大于小时数,则必然无解
速度越快,那么小时数越小,但不是规则变动的
满足条件是要让小时数和给定的去比较
也是从左到右是递减的
如果小时数大于给定的话,就是不满足条件,应当向右侧去找
如果小于的话,是满足条件,应当向左侧去找代价更小的

在这里插入图片描述

class Solution {
public:
    int minEatingSpeed(vector<int>& piles, int h) {
        int rightB=piles[0];
        long long sum=0;
        for(int i:piles){
            sum+=i;
            if(i>=rightB){
                rightB=i;
            }
        }
        int leftB=sum/h;
        //int leftB=((sum/h)?sum/h:1);
        if(piles.size()>=h){
            return rightB;
        }
        if(leftB==0){
            return 1;
        }
        int mid=0;
        // cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"每小时吃的香蕉数为"<<mid<<endl;
        while(leftB<rightB){
            long long tar=0;
            mid=(leftB+rightB)/2;
            for(int i:piles){
                tar+=((i/mid)+((i%mid)?1:0));
            }
            if(tar>h){
              //  cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"每小时吃的香蕉数为"<<mid<<"小时数是"<<tar<<endl;
                leftB=mid+1;
            }else{
                //cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"每小时吃的香蕉数为"<<mid<<"小时数是"<<tar<<endl;
                rightB=mid;
            }
        }
        mid=(leftB+rightB)/2;
        return mid;
    }
};

DEBUG

1

剪枝判断写反了,应当是堆数大于小时数时,就退出,写成如果堆数小于时就退出

2

第二个是二分判断条件写错了,应当是tar>h是不满足条件的,写成了tar>=h,导致会丢掉可行解,而丢掉的可能正是最优解

=的时候是满足条件的,应当保留

除零错误

期望的最小左值一开始设定的是sum/h,如果sum<h的时候,就会使左值为0,
sum<h就是说每小时只吃一个都戳戳有余,如果保留了左值0,那么1肯定满足条件,但是会接着去尝试为0的情况,从而导致了错误

两种解决方法,一种是规定左值最小值为1,另一种就是提前剪枝,如果左值为0,就直接返回1即可

就分析性能而言,应当是提前剪枝的效果更好

3(Mid+1)判断?

产生了死循环
就是tar=h的情况,满足条件且只剩两个值的情况,是保留缩减右侧,尝试继续向左边找结果,那么mid就不应该+1,因为+1就是向右侧去尝试,
而只剩两个值的时候,应当尝试一下左边的,如果继续尝试右边的,就会陷入right=mid,保持死循环;而如果尝试一下左边的,满足条件,就会使right=mid,动了右侧;不满足,就是Left=mid+1.动了左侧,从而避免了死循环
如果是+1的话,就是在除法取到0.5的时候去向上取整
如果不+1,就是有0.5时向下取整
这个取整方向应该和搜索范围缩减的方向相匹配,不然就会导致死循环
如果不满足条件时是left+1,mid是向增大的方向增加,说明向右是满足条件的,此时或许应该不该+1,即尝试左端的left是否满足条件,否则可能会

看只剩最后两个值时的情况,即到底是保留左值还是右值,如果+1就是保留左值,不加就是保留右值
如果陷入了死循环,在+1的情况,说明是去取了右值,
陷入死循环说明此时的值肯定是满足条件的,但是取值的方向和满足条件的方向保持了一致

总结一下就是考虑只剩两个值时候的取值倾向,满足条件时,到底是继续向左还是向右去找值,如果是向左找值,就不应当+1;如果是向右找值,就应当+1;
这个题满足条件就是小时数<=h,速度越快则小时数越小,在满足条件时,应当向左侧找值,所以就不应该+1

1

https://leetcode.cn/problems/maximum-candies-allocated-to-k-children/description/
k个小孩,每个小孩要的糖果数量要一样,如果是n,那么糖果总数就是nk
如果糖果总数和sum小于nk,则必然无解
最好的情况必然是sum/k,是每个小孩能平分到的最大糖果数
每堆数量设为x,

搜索空间是每个小孩分的糖果数,每次搜索的判断是这个糖果数对应出来的堆数
显然每个小孩分的糖果数越多,那么分出来的堆数是越小的,
目标值在搜索空间的从左到右是递减的
那么这样的话,如果搜索值对应到的堆数是比k小的,那么应当是尝试去左侧去找,即让分的糖果数变少,从而增大堆数
目的是要搜索空间的右值,越向右则分的糖果数越多,堆数越少
如果某个区间右值对应中值出来的堆数满足要求,那么还是应当往右靠
如果不满足的话,说明不够,此时应当往左靠
堆数小于k时,应当去

DEBUG

1

第一个错是因为没有考虑如果一开始左边界和右边界就是0的情况,这样的话就不会开启循环,那最后输出的mid,就是没有值的,一个随机值

解决方法

就是在一开始的时候加一个判断,如果左和右都是0的话,直接退出,不返回Mid了,或着让mid一开始就为0,反正也不会进入while循环里,mid也不会被设新值

2

在这里插入图片描述

第二个是因为再次写的时候的一个小聪明,即while退出的条件从leftB<=rightB改为leftB<rightB,考虑的是==的时候必然是要退出循环的,但是<=的时候,在循环内部判断的话,可以给mid赋新值,是只有这个值对应的Mid的值
而如果是<的话,当相等的时候,是直接退出while循环的,而mid没有更新,还是之前的旧值

解决方法

要么最后的时候,即退出while循环后mid再一个赋值等式
要么还是写为<=,在while里判断是否相等
但是性能的话应该是第一种好,因为<=的话会让每次循环都多一个if判断

3

一个致命错误是算堆数的时候是让mid/i,但应该是让i/mid

4

一个致命错误是惯性思维,还是按着如果满足要求就保留右边界right=mid,不满足要求就让left=mid+1的方向去写
但这种写法是只适用于在搜索空间中从左到右,目的值是递增的情况
因为>=k是目的值满足条件的,此时right=mid,相当于在满足条件的基础上进一步缩小搜索空间,以寻找代价更小的解
而<k是不满足条件的,此时需要向满足条件的方向去寻找,left=mid+1就是去剔除掉左侧不满足条件的所有解与mid这个解

当从左到右,目的值是递减的情况时,如果满足条件还保留right=mid,虽然此时的mid时满足条件的,但是相当于自行截掉了右侧所有可能依然满足条件且代价更小的解
如果不满足条件让left=mid+1,则更是荒谬,因为此时mid都不满足了,继续向右找,更是完全找不到
相反,此时应当是满足条件时,保留左侧的这个可行解,而尝试向右侧去找代价更小的解,即满足条件时left=mid,不满足时,向左侧去找,right=mid-1

5

又一个致命错误是死循环的产生
在这里插入图片描述
在这里插入图片描述

在这种样例下,满足条件,保留left不动,那么left一直不动,right也不动,从而造成死循环
在从左到右目的值是递增情况下不会出现这种情况,是因为除法是向下取整的
而这种情况下,由于是向下取整,满足时还left不动,就会导致最后区间只有两个相邻的值,但是/完后是0.5,不会进1,导致一直取下值,造成死循环

中值写为
mid=(leftB+rightB+1)/2
在这里插入图片描述

class Solution {
public:
    int maximumCandies(vector<int>& candies, long long k) {
        long long sum=0;
        for(int i:candies){
            sum+=i;
        }
        int leftB=0;
        int rightB=sum/k;
        int mid=0;
        while(leftB<rightB){
            long long res=0;
            mid=(leftB+rightB+1)/2;
            for(int i:candies){
                res+=(i/mid);
            }
            if(res<k){
                cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"糖果数为"<<mid<<"分的堆数是"<<res<<endl;
                rightB=mid-1;
            }else{
                cout<<"此时左边界为"<<leftB<<"右边界为"<<rightB<<"糖果数为"<<mid<<"分的堆数是"<<res<<endl;
                leftB=mid;
            }
        }
        mid=(leftB+rightB)/2;
        return mid;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数学建模 第三讲 - 简单的优化模型

在数学建模的学习过程中&#xff0c;第三章介绍了几种简单的优化模型&#xff0c;这些模型在实际生活中有广泛的应用。以下是对这些模型的整理和总结。 3.1 存贮模型 问题描述 配件厂为装配线生产产品&#xff0c;更换设备需要支付生产准备费&#xff0c;产量大于需求时需要…

Llama 3.2 微调指南

让我们通过微调 Llama 3.2 来找到一些精神上的平静。 我们需要安装 unsloth&#xff0c;以更小的尺寸实现 2 倍的快速训练 !pip install unsloth!pip uninstall unsloth -y && pip install --upgrade --no-cache-dir "unsloth[colab-new] githttps://github.co…

OpenCV马赛克

#马赛克 import cv2 import numpy as np import matplotlib.pyplot as pltimg cv2.imread(coins.jpg,1) imgInfo img.shape height imgInfo[0] width imgInfo[1]for m in range(200,400): #m,n表示打马赛克区域for n in range(200,400):# pixel ->10*10if m%10 0 and …

初识Linux · 文件(1)

目录 前言&#xff1a; 回顾语言层面的文件 理解文件的预备知识 文件和磁盘 使用和认识系统调用函数 前言&#xff1a; 本文以及下篇文章&#xff0c;揭露的都是Linux中文件的奥秘&#xff0c;对于文件来说&#xff0c;初学Linux第一节课接触的就是文件&#xff0c;对于C…

Windows删除service服务

Windows删除service服务 找到命令提示符&#xff1a; 右键&#xff0c;以管理员身份运行 输入&#xff1a; sc delete 服务名 Windows根据TCP端口号查找进程PID再kill进程_windows tcpkill-CSDN博客文章浏览阅读5.3k次&#xff0c;点赞42次&#xff0c;收藏104次。Windows根据…

【408计算机考研课程】数据结构-数据结构在学什么?

前言 数据结构在学什么&#xff1f; 如何用程序代码把现实世界的问题信息化如何用计算机高效地处理这些信息从而创造价值 第一章&#xff1a;数据结构在学什么&#xff1f; 总览 什么是数据&#xff1f; 简介&#xff1a;数据是信息的载体&#xff0c;是描述客观事物属性的数、…

【在Linux世界中追寻伟大的One Piece】进程信号

目录 1 -> 信号入门 1.1 -> 生活角度的信号 1.2 -> 技术应用角度的信号 1.3 -> 注意 2 -> 信号的概念 2.1 -> 用kill -l命令可以查看系统定义的信号列表 2.2 -> 信号处理常见方式 3 -> 产生信号 3.1 -> Core Dump 3.2 -> 调用系统函数…

已解决-Nacos明明成功运行,但Spring报错连接不上

这天使用windows本地nacos的时候&#xff0c;一直报错&#xff1a; Caused by: com.alibaba.nacos.api.exception.NacosException: Request nacos server failed: Caused by: com.alibaba.nacos.api.exception.NacosException: Client not connected, current status:STARTIN…

(计算机组成原理)

计算机的发展 计算机系统硬件&#xff08;计算机的实体&#xff0c;如主机&#xff0c;外设等&#xff09;软件&#xff08;由具有各种特殊功能的程序组成&#xff09; 硬件是计算机系统的物理基础&#xff0c;硬件决定瓶颈&#xff0c;软件决定性能发挥的程度 第一台电子数字计…

YOLOv4和Darknet实现坑洼检测

关于深度实战社区 我们是一个深度学习领域的独立工作室。团队成员有&#xff1a;中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等&#xff0c;曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万粉丝&#xff0c;拥有2篇国家级人工智能发明专利。 社区特色…

IDEA如何自定义创建类的文档注释

说明&#xff1a;在IDEA中&#xff0c;创建一个Java类文件&#xff0c;会在类上面自动生成文档注释&#xff0c;如下&#xff1a; 看样子&#xff0c;默认是计算机的用户名&#xff0c;然后加上当前的创建时间。可以在IDEA中的Setting中设置&#xff0c;如下&#xff1a; /*** …

汽车追尾为什么是后车的责任?

简单点说&#xff1a;因为人后面没有长眼睛。 结论 在汽车追尾事故中&#xff0c;通常情况下后车被认为是责任方的原因在于交通法规对驾驶安全标准的约定和实践中的责任识别原则。虽然追尾事故常见地被归责于后车&#xff0c;但具体判断并不是绝对的&#xff0c;仍需综合多种…

C++11中的特性

这里主要讲解一些C11相较于C98所新增的比较实用的新特性。 C11的官方文档&#xff1a;C11 - cppreference.comhttps://en.cppreference.com/w/cpp/11 一、列表初始化&#xff08;List-initialization&#xff09; &#xff08;一&#xff09;、使用“{}”进行初始化 在C98中&…

有关自连接表的统一封装

表结构 RecursionBean Getter Setter ToString JsonInclude(JsonInclude.Include.NON_EMPTY) public class RecursionBean<T> extends BaseVO {/*** 编号*/private T id;/*** 父权限ID&#xff0c;根节点的父权限为空* 注释掉JsonIgnore&#xff0c;是为了前端判断是否…

Linux驱动开发常用调试方法汇总

引言&#xff1a;在 Linux 驱动开发中&#xff0c;调试是一个至关重要的环节。开发者需要了解多种调试方法&#xff0c;以便能够快速定位和解决问题。 1.利用printk 描述&#xff1a; printk 是 Linux 内核中的一个调试输出函数&#xff0c;类似于用户空间中的 printf。它用于…

CE找CSGO人物坐标和视角基址-幽络源原创

前言 幽络源站长本次免费分享的是CE找CSGO人物坐标和视角基址 本教程分为两篇&#xff0c;当前为上篇->找基址 所具备的知识 CE的使用 教程目的 通过CE找到一些基地址&#xff0c;然后结合Python实现CSGO的透视绘制&#xff0c;这里我们是纯手写透视。 第一步&#x…

如何使用CMD命令启动应用程序(二)

说明&#xff1a;去年1024发布了一篇博客&#xff0c;介绍如何使用CMD命令启动应用程序&#xff0c;但实际情况&#xff0c;有些程序可能无法用配置环境变量的方式来启动&#xff0c;本文针对两种情况下的程序&#xff0c;如何使用CMD命令来启动&#xff0c;算是对上一篇博客的…

Java开发必知必会的一些工具

本文主要介绍 Java 程序员应该学习的一些基本和高级工具。 如果你想成为一名更好的程序员&#xff0c;最重要的技巧之一就是学习你的编程工具。 Java 世界中存在着如此多的工具&#xff0c;从 Eclipse、NetBeans 和 IntelliJ IDEA 等著名的 IDE 到 JConsole、VisualVM、Eclipse…

class 004 选择 冒泡 插入排序

我感觉这个真是没有什么好讲的, 这个是比较简单的, 感觉没有什么必要写一篇博客, 而且这个这么简单的排序问题肯定有人已经有写好的帖子了, 肯定写的比我好, 所以我推荐大家直接去看“左程云”老师的讲解就很好了, 一定是能看懂的, 要是用文字形式再写一遍, 反而有点画蛇添足了…

计算机视觉算法知识详解(含代码示例)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…