一文带你彻底掌握二分查找

news2024/9/25 21:28:35

1. 认识二分查找

二分查找也被称为折半查找,他是一种查询效率较高的查找方式,普通查找的方式通常是从头到尾遍历一遍数组,二分查找的方式是找到数组中间的那个元素mid与目标值target进行比较,比target小就去前半段找,大则往后半段,所以每次都能舍去一半的空间,也就是指数级别的。二分查找的效率是O(logN)。

如下是二分查找和直接遍历的演示。

最终我们发现二分查找用了3步找到了37,而直接遍历用了11步。你可能会觉得差距不大,那只是因为数据量太小了,如果现在有2^32个数,考虑最坏情况,直接遍历需要2^32次,而二分查找只需要32次。

2. 二分查找的条件

网上很多文章说二分查找的前提是数组有序,这种说法是不严谨的。

其实二分查找的条件是:数组中任意一个数,可以满足:以这个数为边界,可以将数组分成两个部分,将这个数与目标值进行比较,每次都可以舍去其中一个部分,选择另外一个区域继续查找

也就是说只要满足二段性就可以二分

而数组有序就是满足二段性的最常见的场景,数组中任意一个数都可以满足,这个数之前的所有数都是<当前数,这个数后面的所有数都是>=当前数的。

3. 二分查找模板

为什么要学习二分查找的模板,因为二分查找如果不注意非常容易写出死循环,而我们如果记住了二分的模板,可以有效避免这个问题。对于二分的模板,最好是理解性记忆,知道这个模板怎么来的,在这个基础上去记忆。

3.1 朴素二分

二分中最简单的一种情况,也就是上面动图所演示的。朴素二分指的就是要找到数组当中确定的一个数target

以一道例题带大家了解一下,题目链接:704. 二分查找 - 力扣(LeetCode)

首先,这题是满足二段性的(数组有序),所以我们可以使用二分,第二点就是我们要找的是具体一个数,所以采用朴素二分的办法。

朴素二分的步骤:

  • 1. 设置两个指针left,right(这里的指针不是C语言中的指针,只要是可以用来标记区域的都可以称之为指针)。
  • 2. 让mid = (left + right) / 2,即找到left和right的中间点,此时mid对应的值x有三种情况x < target, x > target, x = target。
  • 3. 如果x < target,那么说明target在的位置是一定在mid的右边的,那我们可以让left = mid + 1;如果x > target,那么说明target的位置是一定在mid左边的,我们可以让right = mid - 1;如果x = target,说明mid位置的值就是我们要找的,直接返回即可

注意事项:

  1. 循环结束条件是left <= right,而不是left < right。因为可能存在数组只有一个数,而left,right都为0,此时不会进入二分查找的判断逻辑中。
  2. mid = (left + right) / 2的写法是是向下取整,例如有4个数,进行计算后,mid为第二个数;在朴素二分中,也可以写成向上取整,即mid = (left + right + 1) / 2。
  3. 强调上面两点主要是为了后面两个模板做铺垫。

那我们可以很轻松的写出朴素二分的代码:

while (left <= right)
{
    int mid = left + (right - left >> 1);
    if (nums[mid] > target)
        right = mid - 1;
    else if (nums[mid] < target)
        left = mid + 1;
    else 
        return mid;
}

注意这里的mid的写法,如果mid写成 mid = (left + right) / 2的方式,那么如果left和right比较大的话,mid可能会出现越界的情况,为了避免这种方式,我写成了 mid = left + (right - left) / 2; left其实就是两个数中较小的那个,right - left是两个数据的差值,再除2,就相当于是将right比left多的那一半给了left,这种写法是不会越界的。还有一点就是 / 2可以替换成 >> 1,>>是位运算,>> 1相当与 / 2,<< 1相当于 * 2,位运算的速度是比乘除法快的,不习惯的同学直接写成mid = left + (right - left) / 2。

我们可以开始尝试解决上面那个题目了

class Solution 
{
public:
    int search(vector<int>& nums, int target) 
    {
        int left = 0;
        int right = nums.size() - 1;
        while (left <= right)
        {
            int mid = left + (right - left >> 1);
            if (nums[mid] > target)
                right = mid - 1;
            else if (nums[mid] < target)
                left = mid + 1;
            else 
                return mid;
        }

        //没有找到目标值target
        return -1;
    }
};

3.2 查找左端点和右端点

大部分二分的题目其实是查找左端点以及右端点这类题的,左端点的意思就是一段区域的起始点,右端点和左端点类似,就是一段区域的终止点。

还是以一道例题来说明,题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

先考虑能否使用二分,我们发现数组是有序的,满足二段性,可以使用二分。我们就以示例一为例子,如果我们要找到8的开始位置和结束位置,最快的办法就是找到第一个8的位置(左端点)和最后一个8的位置(右端点)。

我们先考虑,如何找到左端点,也就是第一个8的位置。

  • 1. 设置两个指针left, right。
  • 2. mid = (left + right) / 2,找到中间点,同样中间点mid对应的值x有两种情况,第一种x < target,那么target肯定在x的右边,我们让left = mid + 1;第二种x >= target,这种情况下,左端点可能就是mid,也有可能在mid左边,我们让right = mid。

对于x >= target为什么要将两种情况合成一种情况,在这道题中确实可以分成两种,因为当 x > target的时候,mid的位置必定不可能是左端点,也就是说左端点一定在mid的左边,可以让right = mid - 1,而x = target的时候,mid位置可能是左端点,也可能不是左端点(例如,mid可能指向的是第二个8),此时我们让right = mid。但是对于大部分的找左端点题目来说,x > target不能说明要找的位置在mid左边,为了普适模板,所以我们合并成两种情况。

注意事项:

  • 1. 循环条件要写成left < right,如果写成left <= right,那么mid = left = right时,如果此时的判断条件是x >= target,那么right = mid,也就是说right值不变,下一次还是会经历上面的步骤,最终导致死循环。
  • 2. 求mid要写成mid = (left + right) / 2,而不是mid = (left + right + 1) / 2。如果写成后面那种,同样也会死循环,比如[1, 2], target = 2,left=0,right=1,计算后mid=1也就是2的位置,进行判断,x >= target,也就是让right = mid。right值不变,发生死循环。

根据上面内容,我们可以写成二分查找左端点的模板:

while (left < right)
{
    int mid = left + (right - left>> 1);
    if (....)
        left = mid + 1;
    else 
        right = mid;
}

其中的....需要根据不同的题目来填写。

同样的查找右端点也是同样的道理。

  • 1. 设置两个指针left, right。
  • 2. mid = (left + right) / 2,找到中间点,同样中间点mid对应的值x有两种情况,第一种x <= target,那么mid位置可能就是右端点,也可能不是,让left = mid,第二种情况,x > target,那么右端点一定在mid的左边,让right = mid - 1。

注意事项:

  • 1. 循环条件要写成left < right,如果写成left <= right,那么mid = left = right时,如果此时的判断条件是x >= target,那么right = mid,也就是说right值不变,下一次还是会经历上面的步骤,最终导致死循环。
  • 2. 求mid要写成mid = (left + right + 1) / 2,而不是mid = (left + right) / 2。如果写成后面那种,同样也会死循环,比如[1, 2], target = 1,left=0,right=1,计算后mid=0也就是1的位置,进行判断,x <= target,也就是让left = mid。left值不变,发生死循环。

二分查找右端点代码:

while (left < right)
{
    int mid = left + (right - left + 1 >> 1);
    if (....)
        right = mid - 1;
    else  
        left = mid;
}

其中的....需要根据不同的题目来填写。

这两个模板看着非常相似,主要的区别在于mid是否要+1,我们可以记一下,只要下面出现mid - 1,上面mid就+1

现在有了具体的模板就可以开始写题了,如果套模板,二分只要能找到二段性就非常简单,而且不用担心死循环。

class Solution 
{
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        if (nums.size() == 0)
            return {-1, -1};

        int left = 0;
        int right = nums.size() - 1;
        //先查找区间的左端点
        while (left < right)
        {
            int mid = left + (right - left >> 1);
            if (nums[mid] < target)
                left = mid + 1;
            else 
                right = mid;
        }

        if (nums[left] != target)
            return {-1, -1};
        int begin = left;

        //查找区间右端点
        right = nums.size() - 1;
        while (left < right)
        {
            int mid = left + (right - left + 1 >> 1);
            if (nums[mid] > target)
                right = mid - 1;
            else  
                left = mid;
        }
        
        return {begin, right};
    }
};

4. 二分例题

下面将用几道例题带大家彻底掌握二分。

4.1 x的平方根

题目链接:69. x 的平方根 - 力扣(LeetCode)

这题乍一看和二分没什么关系,那我们可以先考虑暴力解法,就是从1-x进行遍历,依次判断,直到出现满足题意的数。

那么这道题也就转化成了从1-x中查找一个数,因为是有序的,具有二段性,所以我们可以使用二分查找,又因为这道题是要找到最后一个数,这个数的下一个数的平方就大于x了,也就是说我们需要找区间右端点,我们回忆一下右端点的模板。

while (left < right)
{
    int mid = left + (right - left + 1 >> 1);
    if (....)
        right = mid - 1;
    else  
        left = mid;
}

判断条件只需要改成mid * mid > x即可,因为mid位置的平方大于x,那说明mid位置肯定不是平方小于x的最后一个数。最终代码如下:

class Solution 
{
public:
    int mySqrt(int x) 
    {
        int left = 1;
        int right = x;

        while (left < right)
        {
            long long mid = left + (right - left + 1 >> 1);
            if (mid * mid > x)
                right = mid - 1;
            else 
                left = mid;
        }

        return right;
    }
};

4.2 搜索插入位置

题目链接:LCR 068. 搜索插入位置 - 力扣(LeetCode)

在一个有序数组中查找某个值,我们最先想到的就是二分,这道题也是符合二段性的,任意一个数x都能将数组分成两个区间,其中一个区间<x, 另一个区间>=x。这道题的二分判断条件就是找到第一个>=target的数,这个数所在的位置就是答案,返回他的下标即可,也就是找区间的左端点,那我们直接套模板。

需要注意的是,如果target的位置在数组中最后一个位置的后面,这种情况要特殊处理,因为left和right指针只能在数组范围内移动,不能超出数组范围。

class Solution 
{
public:
    int searchInsert(vector<int>& nums, int target) 
    {   
        int left = 0;
        int right = nums.size() - 1;
        //找 >= target的第一个数
        while (left < right)
        {
            int mid = left + (right - left>> 1);
            if (nums[mid] < target)
                left = mid + 1;
            else 
                right = mid;
        }
        
        //如果插入的位置在最后一个位置之后
        if (nums[right] < target)
            return right + 1;
        else 
            return right;
    }
};

4.3 山脉数组的峰顶索引

题目链接:LCR 069. 山脉数组的峰顶索引 - 力扣(LeetCode)

由题目意思可以得出,数组中有且只有一个峰顶,并且峰顶不是数组的第一个和最后一个,我们先考虑这题是否满足二段性,我们随意取一个值x,有三种情况,1.x比右边的值小,那么x肯定不是峰顶元素,也就是说峰顶一定在x的右边,2.x比左边的值小,x肯定不是峰顶,峰顶在x左边,3.x比左右两边的值都大,那么x就是峰顶。

我们发现这道题的数组虽然无序,但是满足二段性,所以我们依然可以使用二分查找来处理问题。

根据上面总结的,可以看出这题是一个朴素二分,直接套模板即可。

class Solution 
{
public:
    int peakIndexInMountainArray(vector<int>& arr) 
    {
        int left = 0;
        int right = arr.size() - 1;
        while (left <= right)
        {
            int mid = left + (right - left >> 1);
            if (arr[mid] < arr[mid + 1])
                left = mid + 1;
            else if (arr[mid] < arr[mid - 1])
                right = mid - 1;
            else    
                return mid;
        }
        return -1;
    }
};

4.4 寻找峰值

题目链接:162. 寻找峰值 - 力扣(LeetCode)

这题和上一题相似,但是不同的是,这题有多个峰值,并且数组的第一个和最后一个元素是可以为峰值的。

那么这题如何得出二段性呢,二段性指的是数组中任意一个数都能满足,这个数能将数组分成两部分,我们可以舍弃一部分,转而去另一部分继续查找。

我们随便取一个数x,设y为x的下一个数,这个数有两种情况,如果x > y,说明x可能是山顶,那我们可以舍弃x右边的那个部分,去x的左边进一步判断;如果x < y,说明x不可能是山顶,并且x右边区域一定有一个山顶,那我们可以舍弃x左边,去右边进一步判断,综上,这题也是具有二段性的。

 

如何编写代码呢,我们先看一下这个属于哪种类型的二分,当x < y时,峰顶在x右边,当x > y时,x可能是峰顶,也可能不是,根据这个逻辑,我们可以看出和查找左端点的逻辑是相同的,尝试一下编写代码。

class Solution 
{
public:
    int findPeakElement(vector<int>& nums) 
    {
        int left = 0;
        int right = nums.size() - 1;

        while (left < right)
        {
            int mid = left + (right - left >> 1);
            if (nums[mid] > nums[mid + 1])
                right = mid;
            else 
                left = mid + 1; 
        }

        return left;
    }
};

这个代码不用担心mid指向最后一个数时,与mid+1进行比较会发生越界访问,因为mid指向最后一个数时,只有可能left和right都指向最后一个数,那么这时不满足循环条件,直接退出了。

4.5 寻找旋转排序数组中的最小值

题目链接:153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

这道题也是二分中比较经典的一题,也很好证明了即使数组不是有序的也能二分。

首先,我们先证明一下这题可以二分,也就是具有二段性。

根据题目所示,将数组分成两个部分,AB和CD,其中AB中任意一点都比CD中任意一点要大,我们最终要找的点是C点。其中有两个比较特殊的点,A和D,我们以D为例,我们可以发现一个现象,AB中任意一个数都比D要大,CD中任意一个数都小于等于D(可能为D本身)

现在我们任意取一个数x,如果x是AB中的某一个数(x > D),那么说明x的左边肯定是没有结果的,我们可以舍弃左边取x的右边查找,同理,如果x是CD中的某一个数(x <= D),那么说明接下来要去x的左边查找。

综上,我们可以得到二段性,也就满足了二分条件。这题很明显是找区间左端点,那么套模板,很容易就能写出代码。

class Solution 
{
public:
    int findMin(vector<int>& nums)
    {
        int n = nums.size() - 1;
        int left = 0;
        int right = n;

        //当mid > nums[n]时,那么mid肯定不是最小的那个数,所以让left = mid + 1
        //当mid < nums[n]时,mid可能是最小的那个值,让right = mid
        while (left < right)
        {
            int mid = left + (right - left >> 1);
            if (nums[mid] > nums[n])
                left = mid + 1;
            else 
                right = mid;
        }
        return nums[left];
    }
};

4.6 0~n-1中缺失的那个数

题目链接:LCR 173. 点名 - 力扣(LeetCode)

这题也是剑指offer中的一题,有很多种解法,例如:

  • 1.哈希表(创建一个大小为n+1的哈希表,遍历一遍数组,将出现过的数填入哈希表中,然后再次遍历找到哈希表中没有出现过的数)
  • 2.直接遍历(使用一个i从0到n,如果records[i] != i,那么就是缺失的那个数)
  • 3.等差数列求和(使用等差数列求和公式求出首项为1,尾项为n的值,再减去数组中每一个数就能得出缺失的那个数)
  • 4.使用位运算(位运算的特点是a ^ a = a,a ^ 0 = 0,根据这个特点,我们将0~n-1全部异或一遍,再和数组中的值全部异或一遍,就能得出结果)

但是这道题目最优的解法其实是二分查找,只不过二分条件不容易想到,其实只要仔细观察我们就会发现,数组和下标一样从0开始,并且每次+1,也就是说,缺失的那个数开始,数组和下标就对应不上了。

二段性也比较好证明,如果一个数组值和下标值相同,那么这个数的左边就可以舍去了,转而去这个数的右边查找,如果一个数组值和下标值不同,说明这个数左边或者这个数本身出问题了,舍弃这个数右边的部分,去查找左边。

这题很明显是找左端点,因为找的是第一个数组值和下标值不同的,所以套左端点模板即可。

需要注意的是,如果0~n-1的数在数组中全部出现了,也就是说缺失的其实是n,也就超出了数组范围,我们需要特殊处理一下。

class Solution 
{
public:
    int takeAttendance(vector<int>& records) 
    {
        //如果要找的数不在数组[rescords[left], rescords[right]]范围内
        //直接返回结果
        int n = records.size();
        if (n != records[n - 1])
            return n;

        //二分规律:[0,1,2,3,5]
        //对应下标: [0,1,2,3,4]
        //我们发现缺少的那个数就是数值与下标不同的第一个数,满足二段性
        int left = 0;
        int right = n - 1;
        while (left < right)
        {
            int mid = left + (right - left >> 1);
            if (records[mid] == mid)
                left = mid + 1;
            else 
                right = mid;
        }

        return left;
    }
};

5. 总结

二分是查找算法中比较高效的,时间复杂度是O(logN),当面临需要查找的问题时,如果满足二段性,我们就可以使用二分查找来提高查找效率。

模板最好也要记住,因为二分非常容易写出死循环,在查找左右端点的那里,我们也分析了循环条件不对,mid是否需要+1都会导致死循环的出现,在实际写代码时,我们需要注意避免这些问题。

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

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

相关文章

Java中List集合去重

反问问题&#xff1a;为什么不直接使用 Set 或者 LinkedHashSet 呢 实际场景&#xff1a;实际的业务开发中遇到的情况会更复杂。比如&#xff0c;List 集合可能是历史遗留问题&#xff0c;也有可能是调用接口返回的类型限制&#xff0c;只能使用 List 接收&#xff0c;又或者是…

Qualcomm Linux 交叉编译应用程序

1. 前提条件 Ubuntu 20.04 系统 Qualcomm RB3 Gen2开发板 2.下载并安装 eSDK 平台 1.从 Qualcomm 发布存档平台下载 eSDK。 wget https://artifacts.codelinaro.org/artifactory/qli-ci/flashable-binaries/qimpsdk/qcm6490/x86/qcom-6.6.28-QLI.1.1-Ver.1.1_qim-product-s…

消除数字球-第15届蓝桥省赛Scratch初级组真题第5题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第184讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

python程序使用nohup后台执行不能实时输出到定向文件的解决方法

问题描述&#xff1a;使用nohup命令后台执行python&#xff0c;但python中print方法打印结果不能实时输出到nohup后台定向文件&#xff0c;只能在程序结束时一次性输出。典型问题样例&#xff1a;在python中使用了os.system(command)方法&#xff0c;command命令打印的结果可以…

免费爬虫软件“HyperlinkCollector超链采集器v0.1”

HyperlinkCollector超链采集器单机版v0.1 软件采用python的pyside2和selenium开发,暂时只支持window环境&#xff0c;抓取方式支持普通程序抓取和selenium模拟浏览器抓取。软件遵守robots协议。 首先下载后解压缩&#xff0c;然后运行app目录下的HyperlinkCollector.exe 运行…

网页与App无缝衔接,揭秘拉起应用的黑科技!

随着移动互联网的飞速发展&#xff0c;App已经成为了我们日常生活中不可或缺的一部分。然而&#xff0c;在推广和运营App的过程中&#xff0c;如何让用户更便捷地从网页跳转到App&#xff0c;一直是困扰推广者的难题。今天&#xff0c;我们就来聊聊网页拉起应用这一黑科技&…

开源 AI 智能名片 S2B2C 商城小程序中的全渠道供应策略

摘要&#xff1a;本文深入探讨在开源 AI 智能名片 S2B2C 商城小程序的情境下&#xff0c;全渠道供应的运行机制。阐述各环节企业相互配合的重要性&#xff0c;重点分析零售企业在其中的关键作用&#xff0c;包括协调工作、信息传递、需求把握等方面&#xff0c;旨在实现高效的全…

Python中的上下文管理器:提升代码的优雅与安全

在编写Python程序时&#xff0c;处理资源&#xff08;如文件、网络连接、数据库会话等&#xff09;的正确打开和关闭至关重要。不当的资源管理可能导致内存泄漏、数据损坏等问题。幸运的是&#xff0c;Python提供了一种优雅的方式来解决这个问题——上下文管理器。本文将探讨上…

象过河轮胎进销存,轻松管理进出库以及废旧轮胎回收

在轮胎行业&#xff0c;高效的进销存管理与废旧轮胎的回收是两大核心挑战&#xff0c;象过河轮胎进销存应运而生。软件专为轮胎行业量身定制&#xff0c;从进货&#xff0c;销售&#xff0c;到库存盘点&#xff0c;财务&#xff0c;轮胎废旧回收等一体化管理&#xff0c;以科技…

【每日刷题】Day118

【每日刷题】Day118 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 123. 买卖股票的最佳时机 III - 力扣&#xff08;LeetCode&#xff09; 2. 188. 买卖股票的最佳时…

如何用python打开csv文件路径

python读取CSV文件方法&#xff1a; 方法1&#xff1a;可先用以下代码查看当前工作路径&#xff0c;然后将CSV文件放在该路径下。 import os os.getcwd() 方法2&#xff1a;&#xff08;绝对路径&#xff09; import pandas as pd iris_trainpd.read_csv(E:\Study\DataSets\ir…

树莓派最强大的应用商店,你知道嘛?

Pi-Apps —— Raspberry Pi 最强大的应用商店 不久前&#xff0c;我获得了我的第一台Raspberry Pi。和大多数从Windows转移到Linux&#xff08;特别是Raspberry Pi用户&#xff09;的用户一样&#xff0c;我被这些问题困扰得快要疯了&#xff1a; 如何在我的Raspberry Pi上安装…

vue3 响应式API customRef()

使用ref()定义响应式数据&#xff1a; <template><div><div>{{ inputValue }}</div><input type"text" v-model"inputValue"></div> </template> <script setup lang"ts"> import { ref } fro…

测测万用表?合宙功耗分析仪Air9000Air9000P齐出动

当心&#xff01;那个被你遗忘的万用表&#xff0c;可能正在偷偷“吃”电&#xff01; 万用表不关是一个常见的错误&#xff0c; 指的是在使用万用表进行测量后&#xff0c;没有关闭或断开电路而直接离开。 这样做可能会导致电池耗尽&#xff0c;影响测量结果&#xff0c;甚…

解决报错 ‘numpy‘ has no attribute ‘bool8‘. Did you mean: ‘bool‘?

定位到报错的文件中 将所有bool8都修改为bool_ 一开始按照错误信息提示修改源码为bool还是会显示错误 这是因为&#xff1a; 目前最新的的NumPy版本版本中布尔类型的接口已经改为bool_。 通过查找可以定位到所有错误使用了bool8的位置&#xff08;上图是我修改之后再次查询…

什么是CPU、GPU、NPU?(包懂+会)

目录 举例子 CPU&#xff1a;主厨 GPU&#xff1a;大量的厨房助理 NPU&#xff1a;面包机 总结 讲理论 CPU&#xff08;中央处理器&#xff09; GPU&#xff08;图形处理单元&#xff09; NPU&#xff08;神经网络处理单元&#xff09; 对比分析 举例子 CPU&#xff…

【CAPL实战】解决调用LIN函数不生效问题

测试背景&#xff1a; 被测样件为LIN节点&#xff0c;进行LIN唤醒测试。添加LDF文件后&#xff0c;在vTESTstudio中编写CAPL自动化脚本通过调用LINwakeup()函数来实现唤醒测试。测试结果是唤醒不成功&#xff0c;通过示波器观测发现并没有唤醒脉冲发出&#xff0c;也就是说调用…

JVM基础概念

一、JVM概述 1. 为什么要学习JVM&#xff1f; 线上系统突然宕机&#xff0c;系统⽆法访问&#xff0c;甚⾄直接 OOM &#xff1b; 线上系统响应速度太慢&#xff0c;优化系统性能过程中发现 CPU 占⽤过⾼&#xff0c;原因是因为 JVM 的 GC 次 数过于频繁&#xff1b; 新项⽬…

OJ 最接近的三数之和

题目&#xff1a; 给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数&#xff0c;使它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在恰好一个解。 提示&#xff1a; 3 < nums.length < 1000-1000 < nums[i] < …

【计算机毕设-软件开发类】基于SpringBoot的食品安全管理平台

&#x1f497;博主介绍&#xff1a;✌全平台粉丝5W,高级大厂开发程序员&#x1f603;&#xff0c;博客之星、掘金/知乎/华为云/阿里云等平台优质作者。 【源码获取】关注并且私信我 【联系方式】&#x1f447;&#x1f447;&#x1f447;最下边&#x1f447;&#x1f447;&…