LeetCode题集-4 - 寻找两个有序数组的中位数,图文并茂,六种解法,万字讲解

news2024/12/23 19:37:20

题目:给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

在这里插入图片描述

作为目前遇到的第一个困难级别题目,我感觉这题还是挺难的,研究了三天总算研究明白了,下面就给大家分享一下这题的几种解法,请大家跟着我的思路循序渐进地来解这道题。

01解法一、娱乐法(内置方法)

鉴于这题的难度,我们先来一个娱乐解法给大家先放松一下,而且在项目上遇到这样的需求我想绝大多少都会用这种方式直接处理,而且这种思路更符合人的最直观思维。简单说下思路,就是直接用语言自带的方法先把两个数组拼接起来,然后对拼接后的数组排序,最后根据数组个数奇偶直接获取值。代码如下:

//使用内置方法直接合并数组并排序法
public static double MergedArraySort(int[] nums1, int[] nums2)
{
    var merged = nums1.Concat(nums2).ToArray();
    Array.Sort(merged);
    var len = merged.Length;
    if (len % 2 == 0)
    {
        return (merged[(len / 2) - 1] + merged[len / 2]) / 2.0;
    }
    else
    {
        return merged[len / 2];
    }
}

当然我们做算法题是为了研究算法思维,寻找更优的解决方案,因此我们继续探索更算法的解法。

02解法二、双指针+合并数组法

虽说上面的解法有娱乐的成分,但是却也提供了最直观的思路,我们沿用上面的整体思路,把调用语言内置方法的地方改为我们自己实现。先梳理一下整体思路。

1.首先构建一个可以容纳两个数组的大数组;

2.然后同时从两个数组的起始位置开始比较,如果值较小则进入大数组,值较大则继续和后面元素比较;

3.处理数组1没有数据,数组2有数据的情况;

4.处理数组2没有数据,数组1有数据的情况;

5.处理其他情况;

6.这样两个数组元素都处理完成,则数组合并完成,并且是有序的;

7.根据大数组元素个数奇偶取出中位数。

可以看看下图实际步骤详解:

在这里插入图片描述

实现代码如下:

//双指针合并数组后取值法
public static double TwoPointerMergedArray(int[] nums1, int[] nums2)
{
    //创建一个可以容纳两个数组的大数组
    var list = new int[nums1.Length + nums2.Length];
    //数组1的索引
    var idx1 = 0;
    //数组2的索引
    var idx2 = 0;
    //大数组的索引
    var idx = 0;
    //如果两个数组都处理完成则退出,每次大数组索引前进1位
    while (idx1 < nums1.Length || idx2 < nums2.Length)
    {
        if (idx1 >= nums1.Length)
        {
            //如果数组1没有数据了,则把数组2当前值填充至大数组后,把数组2索引前进1位
            list[idx] = nums2[idx2++];
        }
        else if (idx2 >= nums2.Length)
        {
            //如果数组2没有数据了,则把数组1当前值填充至大数组,把数组1索引前进1位
            list[idx] = nums1[idx1++];
        }
        else
        {
            //当两个数组都有数据,则比较当前两个数,数小的则填充至大数组,并且把其对应的索引前进1位
            if (nums1[idx1] < nums2[idx2])
            {
                list[idx] = nums1[idx1++];
            }
            else
            {
                list[idx] = nums2[idx2++];
            }
        }
        //大数字索引前进1位
        idx++;
    }
    if (idx % 2 == 0)
    {
        //偶数则取中间两位数,并取平均数
        return (list[(idx / 2) - 1] + list[idx / 2]) / 2.0;
    }
    else
    {
        //奇数则去中间一位数据直接返回
        return list[idx / 2];
    }
}

因为构建了大数组所以空间复杂度是O(n+m),因为把两个数组都遍历了一遍,所以此算法时间复杂度为O(n+m),因此不能满足题目要求的O(log(n+m))。虽然此解法不是我们所要的,但是也给我们继续寻找更好解法提供了逻辑基础。

03解法三、双指针+直接取值法

想想题目,我们是要求中位数,回想一下上一节的解法,我们完全没有必要构建一个大数组,也没有必要把两个数组都处理完,只有找到中位数就可以了,也就是如果偶然我们只需要取两个值,而如果是奇数我们只需要取一个值。因此上一节解法中存在大量没必要的计算。

因此上一节整个解题思路架构可以保留下来,把不必要的去掉即可,思路如下:

1.同时从两个数组的起始位置开始比较,如果值较小则存入临时变量中;

2.处理数组1没有数据,数组2有数据的情况;

3.处理数组2没有数据,数组1有数据的情况;

4.处理其他情况;

5.到达右中位数位置处,则结束处理;

6.根据奇偶和临时变量取出中位数。

先来看代码:

//双指针直接取值法
public static double TwoPointerDirectValue(int[] nums1, int[] nums2)
{
    //两个数组总长度
    var len = nums1.Length + nums2.Length;
    //左数,偶数:左中位数,奇数:中位数前一位
    var left = 0;
    //右数,偶数:右中位数,奇数:中位数
    var right = 0;
    //数组1索引
    var idx1 = 0;
    //数组2索引
    var idx2 = 0;
    //当中位数位处理完则退出
    while (true)
    {
        //把上一个值先放入左数,右数存当前值
        left = right;
        if (idx1 >= nums1.Length)
        {
            //如果数组1没有数据了,则把数组2当前值放入右数,把数组2索引前进1位
            right = nums2[idx2++];
        }
        else if (idx2 >= nums2.Length)
        {
            //如果数组2没有数据了,则把数组1当前值放入右数,把数组1索引前进1位
            right = nums1[idx1++];
        }
        else
        {
            //当两个数组都有数据,则比较当前两个数,数小的则放入右数,并且把其对应的索引前进1位
            if (nums1[idx1] < nums2[idx2])
            {
                right = nums1[idx1++];
            }
            else
            {
                right = nums2[idx2++];
            }
        }
        //当中位数位处理完则结束处理
        if (len / 2 == idx1 + idx2 - 1)
        {
            break;
        }
    }
    if (len % 2 == 0)
    {
        //偶数则取左数和右数平均数
        return (left + right) / 2.0;
    }
    else
    {
        //奇数则直接返回右数
        return right;
    }
}

通过代码可以发现和上一节解法有几个小差别以及关键的。

1.去掉了大数组,改为添加了left,right两个变量来存储中位数;

  1. 关键点“left = right;”,因为我们需要存储两个值来应对奇偶两种情况,所以这里就采用了一个小技巧,每次迭代开始计算前,先把上一次计算结果存放到left中,然后用right存储当前计算结果,所以left,right就是两个连续的元素。如果在一次计算中把要的两个数同时取出来,显然情况会非常复杂难以处理,而这个小技巧正好解决了这个问题使得可以在两次计算中分别取出想要的元素。

3.关键点为什么if (len / 2 == idx1 + idx2 - 1)作为结束处理标志,首先为什么idx1 + idx2 – 1要减1,这是因为在上面取值的同时都进行了后++操作,所以这里要把这个1减掉。然后为什么是len / 2作为结束点,因为/是向下取整,因此一个偶然和比这个偶数大1的奇数,在经过/2处理后值是一样的,又因为len表示的是数组元素个数,而元素个数正好是比索引大1,因此当数组为奇数时len/2就表示中位数,当数组为偶数时len/2就表示右中位数,因此可以视为处理结束标志。

因为去掉了构建大数组因此空间复杂度是O(1),因为处理的数据量减少了一半因此时间复杂度是O((n+m)/2),也就是O(n+m)。因此还是没满足要求。

04解法四、双指针+二分排除法

显然要想达到O(log(n+m))的时间复杂度,就不能一个元素一个元素处理,这不由的让我们想到二分思想,既然一个一个处理不行,那么我们就一次多处理几个,把不满足要求的数据都排除掉,一次排除一半,这样不就离我们的目标值越来越近了嘛,最后就到达我们的目标值了。

我们继续沿用上一节解法思想框架,做一些改动,思路如下:

1.同时从两个数组的左中位数位置的一半处开始比较,如果值较小则排除掉;

2.处理数组1没有数据,数组2有数据的情况,并结束处理;

3.处理数组2没有数据,数组1有数据的情况,并结束处理;

4.处理指针停留在左中位数位置的情况,并结束处理;

5.一次比较完成后,继续取当前位置到左中位数位置的一半处,进行下一次比较;

6.根据奇偶和临时变量取出中位数。

下面我们结合图例来做个详细说明。

在这里插入图片描述

找到左中位数也就意味着可以同时直接处理左右中位数。

我们在来看看代码实现:

//双指针二分排除查找法
public static double TwoPointerBinaryExclude(int[] nums1, int[] nums2)
{
    //数组总长度
    var len = nums1.Length + nums2.Length;
    //目标数,表示中位数在数组中第几个数,偶数则代表左中位数,奇数则代表中位数
    var target = (len + 1) / 2;
    //左中位数
    int left;
    //右中位数
    int right;
    //数组1索引
    var idx1 = 0;
    //数组2索引
    var idx2 = 0;
    //当中位数位处理完则退出
    while (true)
    {
        //数组1没数据了,则直接在数组2中获取中位数
        if (idx1 >= nums1.Length)
        {
            //因为数组1没有数据了,所以数组2索引前进到目标数处
            idx2 += target - 1;
            //直接取中位数
            if (len % 2 == 0)
            {
                //偶数
                //左中位数为当前值
                left = nums2[idx2];
                //右中位数为下一个值
                right = nums2[idx2 + 1];
            }
            else
            {
                //奇数,左右中位数相同都是当前值
                left = right = nums2[idx2];
            }
            break;
        }
        //数组2没数据,则直接在数组1中获取中位数
        if (idx2 >= nums2.Length)
        {
            //因为数组2没有数据了,所以数组1索引前进到目标数处
            idx1 += target - 1;
            //直接取中位数
            if (len % 2 == 0)
            {
                //偶数
                //左中位数为当前值
                left = nums1[idx1];
                //右中位数为下一个值
                right = nums1[idx1 + 1];
            }
            else
            {
                //奇数,左右中位数相同都是当前值
                left = right = nums1[idx1];
            }
            break;
        }
        //当目标数为1时,表明当前值就是要找的值
        if (target == 1)
        {
            //直接取中位数
            if (len % 2 == 0)
            {
                //偶数
                if (nums1[idx1] < nums2[idx2])
                {
                    //如果nums1当前值比较小,则查看其之后是否还有元素
                    if (idx1 + 1 > nums1.Length - 1)
                    {
                        //如果其之后没有元素,则左中位数为nums1当前值,右中位数为nums2当前值
                        left = nums1[idx1];
                        right = nums2[idx2];
                    }
                    else
                    {
                        //如果其之后有元素,则左中位数为nums1当前值,右中位数则为nums1当前值后一个值和nums2当前值中较小值
                        var temp = nums1[idx1 + 1];
                        left = nums1[idx1];
                        right = Math.Min(nums2[idx2], temp);
                    }
                }
                else
                {
                    //如果nums2当前值比较小,则查看其之后是否还有元素
                    if (idx2 + 1 > nums2.Length - 1)
                    {
                        //如果其之后没有元素,则左中位数为nums2当前值,右中位数为nums1当前值
                        left = nums2[idx2];
                        right = nums1[idx1];
                    }
                    else
                    {
                        //如果其之后有元素,则左中位数为nums2当前值,右中位数则为nums2当前值后一个值和nums1当前值中较小值
                        var temp = nums2[idx2 + 1];
                        left = nums2[idx2];
                        right = Math.Min(nums1[idx1], temp);
                    }
                }
            }
            else
            {
                //奇数,左右中位数相同,取nums1当前值和nums2当前值中较小值
                left = right = Math.Min(nums1[idx1], nums2[idx2]);
            }
            break;
        }
        //取出目标数位置的一半
        var half = target / 2;
        //确定nums1比较数,并确保其不会大于自身长度
        var compare1 = Math.Min(idx1 + half, nums1.Length);
        //确定nums2用比较数,并确保其不会大于自身长度
        var compare2 = Math.Min(idx2 + half, nums2.Length);
        //比较两个数组比较数,compare-1因为比较数表示第几个数,减1转为索引
        if (nums1[compare1 - 1] < nums2[compare2 - 1])
        {
            //nums1的比较数 小,则排除掉
            //要查找的目标数需要减掉已经排除掉的个数
            target -= compare1 - idx1;
            //同时nums1当前索引前进到被排除掉元素的后一位
            idx1 = compare1;
        }
        else
        {
            //nums2的比较数 小,则排除掉
            //要查找的目标数需要减掉已经排除掉的个数
            target -= compare2 - idx2;
            //同时nums2当前索引前进到被排除掉元素的后一位
            idx2 = compare2;
        }
    }
    return (left + right) / 2.0;
}

虽然上面代码还很符合我们的思维理解方式的,但是有两个问题需要好好思考:

1.为什么当两个数组值比较时值较小的数及其之前的数可以直接被排除掉?

2.三处结束处理标记代码块中都包含了对奇偶不同情况的处理,使得代码冗长复杂,有什么更好的处理方式吗?

05解法五、双指针+二分查找第K小数法

在这个解法里,我们来解答上面的两个问题。

其实上一节解法中已经包含了二分查找第K小数的核心代码,因为我们在上一解法中最核心的就是先找到左中位数,所以只要把获取右中位数处理及奇偶判断相关代码删掉,其实就得到一份二分查找第K小数算法了,然后分别把左第K小数和右第K小数分别带入算法得到左中位数和右中位数,最后平均得到中位数。

具体代码如下:

//双指针二分查找第K小数法
public static double TwoPointerBinaryFindKth(int[] nums1, int[] nums2)
{
    //左第K小数,当为偶数,代表左中位数,当为奇数,代表中位数
    var leftKth = (nums1.Length + nums2.Length + 1) / 2;
    //右第K小数,当为偶数,代表右中位数,当为奇数,代表中位数(因为/向下取整特性)
    var rightKth = (nums1.Length + nums2.Length + 2) / 2;
    //获取左中位数
    var left = TwoPointerBinaryFindKth(leftKth, nums1, nums2);
    //获取右中位数
    var rigth = TwoPointerBinaryFindKth(rightKth, nums1, nums2);
    return (left + rigth) / 2.0;
}
//双指针二分查找第K小数
public static int TwoPointerBinaryFindKth(int kth, int[] nums1, int[] nums2)
{
    //数组1索引
    var idx1 = 0;
    //数组2索引
    var idx2 = 0;
    //找到第K小数则退出
    while (true)
    {
        //数组1没数据了,则直接在数组2中查找K
        if (idx1 >= nums1.Length)
        {
            //因为数组1没有数据了,所以数组2索引前进到K数索引处
            idx2 += kth - 1;
            return nums2[idx2];
        }
        //数组2没数据,则直接在数组1中查找K
        if (idx2 >= nums2.Length)
        {
            //因为数组2没有数据了,所以数组1索引前进到K数索引处
            idx1 += kth - 1;
            return nums1[idx1];
        }
        //当第K小数为1时,表明当前值就是要找的值
        if (kth == 1)
        {
            return Math.Min(nums1[idx1], nums2[idx2]);
        }
        //取出第K小数位置的一半
        var half = kth / 2;
        //确定nums1比较数,并确保其不会大于自身长度
        var compare1 = Math.Min(idx1 + half, nums1.Length);
        //确定nums2用比较数,并确保其不会大于自身长度
        var compare2 = Math.Min(idx2 + half, nums2.Length);
        //比较两个数组比较数,compare-1因为比较数表示第几个数,减1转为索引
        if (nums1[compare1 - 1] < nums2[compare2 - 1])
        {
            //nums1的比较数 小,则排除掉
            //要查找的第K小数需要减掉已经排除掉的个数
            kth -= compare1 - idx1;
            //同时nums1当前索引前进到被排除掉元素的后一位
            idx1 = compare1;
        }
        else
        {
            //nums2的比较数 小,则排除掉
            //要查找的第K小数需要减掉已经排除掉的个数
            kth -= compare2 - idx2;
            //同时nums2当前索引前进到被排除掉元素的后一位
            idx2 = compare2;
        }
    }
}

通过代码可以发现这个解法已经解决了上一节中的第二个问题,通过对奇偶两种情况左右中位数是第几位数的表示进行了统一,解决了需要判断奇偶做不同处理逻辑的问题,同时因为只关心一个数K,所以整个代码就简洁清晰了。

对比也能发现两个解法最核心的二分思想部分代码完全一样,差别就是一次处理一个值和一次处理两个值的差别。

这里面也有问题和上一个解法第一个问题一样,为什么当两个数组值比较时值较小的数及其之前的数可以直接被排除掉?

下面我们就来详细讲解一下这里可以直接排除掉的原理。

先梳理一下思路,为什么可以直接排除?如果我能证明每次排除的数都是再第K小的左边那边排除不就成立了嘛,因此上面的问题可以转化为证明每次排除掉的数都在第K小数的左边。而我们一直再从左边排除数,因此左边一直再变化着,但是右边的数一直没有变,因此我们再把问题转换一下,我们来证明被排除数右边的数量大于等于(n-k+1)个数。

下面我们以下面两个数组第一次排除为例,做个详细证明,其他次排除同理。

nums1:[1,4,8,9,11]

nums2:[2,3,5,6,7,10]

因为两数组总个数为11,所以我们找第6小数,即k为6,因此第一次从第6/2=3个数开始比较,即比较num1[idx2]与num2[idx2]。如下图。

在这里插入图片描述

因为num1[idx2] > num2[idx2],所以[2,3,5]需要排除掉,因此要证明红框中的个数一定是大于等于(n-k+1=11-6+1=6)。

首先看数组nums1因为8>5所以8肯定是被留下的,因此nums1至少有(len1-idx2)个数是大于5的,再看数组num2因为5要被排除,所以num2有(len2-(idx2+1))个数大于5。

因此大于5的数至少有[(len1-idx2)+(len2-(idx2+1))]

又因为idx2=k/2

所以大于5的数至少有[(len1-k/2)+(len2-(k/2+1))]

化简可得[len1+len2-k+1]

即[n-k+1]

同理如果数组为偶数也可证明,因此为能证明每次排的数右侧都剩下大于等于(n-k+1)个数,因此就证明排除的数肯定是再第K小数的左边可以直接排除,并且从上图可以发现排除的数并不一定是顺序的,但是这不重要,只有在第K小数左边就可以删除。

06解法六、双指针+二分查找平分法

上面两种二分解法已经满足题目要求了,但是这一题还有更优的解法,我们继续来探索。

首先思考一下中位数有什么特性,我们是否能用中位数自身的特性来解题呢?

中位数又叫中值,就是按顺序排列的一组数据中居于中间位置的值,由此我们可以得出两个特性:

1.以中位数为分界线,则中位数左右两边数据个数相等;

2.以中位数为分界线,则中位数左边的最大数必然小于等于中位数右边的最小数。

那要如何应用这两个特性来解题呢?

在这里插入图片描述

如上图,就是如果我们找到一条线来分割两个数组,同时以分割线为准,分割线左边部分看作一个整体当作中位数左边,分割线右边部分看作一个整体当作中位数右边,如果左右两部分能满足上面两个特性就意味着这题就解决了,题目就转变成求解这条分割线了。

显然上图是不可能满足的因为数组总算是奇数,因此无论怎么分割左边和右边数都不可能相当的,除非把分割线画在一个数上,把一个数一分两半,显然不可能,这就没法满足第一个特性。

遇到问题就解决问题,既然对于奇数问题,不能使左右两边相等,那么我们就使得一边总比另一边多一个数总可以了吧,因为有序特性,因此这样的改变并不会影响第二个特性,也就相当于满足了两个特性。

到这里解题的核心思想框架就有了:找到一条分割两个数组的线,使得其能直接或间接满足中位数的两个特性。

核心思想框架有了,接下来就要填充关键逻辑处理点了。

第一个问题:从哪里开始处理?

当然我们还需要继续采用二分思想,因此从数组1中间位置开始处理。

第二个问题:既然从数组1中间开始位置处理,那么数组2怎么办?怎么取值?

并且当数组1当前索引idx1确定后数组2当前索引idx2就可以通过idx1计算得到。

因为当数组总长为偶数时,左右两边个数相等,所以可以得到

idx1+idx2=len1-idx1+len2-idx2,即idx2=(len1+len2)/2 – i;

当数组总长为计算时,我们设定左边比右边多一个数,可以得到

idx1+idx2=len1-idx1+len2-idx2+1,即idx2=(len1+len2+1)/2 – i;

第三个问题:既然分割线是要找出来的那,又是从数组1中间位置开始处理,那应该如何确定往那个方向找呢?

可以通过分割后左边最大值和右边最小值比较确定往那个方向找,就是要做到前进的方向可以把较小值分到左边,较大值分到右边。

第四个问题:处理的节奏是什么?

第一个问题也说了,因为我们还是继续采用二分思想,所以每次处理都会取上次的一半。

第五个问题:还有什么要注意的点吗?

剩下的就是对特殊边界的处理问题了,比如分割到数组的两端,以及数组总数奇偶性单独处理的部分。

到这里整个解题思路就梳理完成了,主要梳理了核心思想框架以及关键逻辑处理点,并没有具体去说有哪些特色边界问题、如何判断前进方向等,因为我感觉核心思想框架+关键逻辑处理点才是重中之重,然后我们再辅助一些图例,最后再看源码,基本就是一看就明白了。

下面以图例来看看两个数组应用二分查找平分法的完整查找过程。

在这里插入图片描述

具体代码如下:

//双指针二分查找平分法
public static double TwoPointerBinaryHalves(int[] nums1, int[] nums2)
{
    //当数组1长度比数组2长度大,则交换两个数组,保证数组1为较短的数组
    if (nums1.Length > nums2.Length)
    {
        //交换两个变量的值,C#7.0中 元组解构赋值 语法
        (nums1, nums2) = (nums2, nums1);
    }
    //左指针
    int idxLeft = 0;
    //右指针
    int idxRight = nums1.Length;
    //对数组1进行二分查找
    while (idxLeft <= idxRight)
    {
        //计算得到数组1分割后的当前索引
        var idx1 = (idxLeft + idxRight) / 2;
        //计算得到数组2分割后的当前索引
        var idx2 = ((nums1.Length + nums2.Length + 1) / 2) - idx1;
        //当数组2左边最大值大于数组1右边最小值时,左指针向右前进
        if (idx2 != 0 && idx1 != nums1.Length && nums2[idx2 - 1] > nums1[idx1])
        {
            idxLeft = idx1 + 1;
        }
        //当数组1左边最大值大于数组2右边最小值时,右指针向左前进
        else if (idx1 != 0 && idx2 != nums2.Length && nums1[idx1 - 1] > nums2[idx2])
        {
            idxRight = idx1 - 1;
        }
        else
        {
            //左边最大值
            int leftMax;
            //如果分割到数组1的最左边即数组1当前索引为0,则左边最大值直接取数组2
            if (idx1 == 0)
            {
                leftMax = nums2[idx2 - 1];
            }
            //如果分隔到数组2的最左边即数组2当前索引为0,则左边最大值直接取数组1
            else if (idx2 == 0)
            {
                leftMax = nums1[idx1 - 1];
            }
            //否则左边最大值为数组1和数组2中左边较大的值
            else
            {
                leftMax = Math.Max(nums1[idx1 - 1], nums2[idx2 - 1]);
            }
            //如果数组为计算,则直接返回左边最大值,结束计算
            if ((nums1.Length + nums2.Length) % 2 == 1)
            {
                return leftMax;
            }
            //右边最小值
            int rightMin;
            //如果分隔到数组最右边即数组1索引为其自身长度,则右边最小值直接取数组2
            if (idx1 == nums1.Length)
            {
                rightMin = nums2[idx2];
            }
            //如果分隔到数组最右边即数组2索引为其自身长度,则右边最小值直接取数组1
            else if (idx2 == nums2.Length)
            {
                rightMin = nums1[idx1];
            }
            //否则右边最小值为数组1和数组2中右边较小的值
            else
            {
                rightMin = Math.Min(nums2[idx2], nums1[idx1]);
            }
            return (leftMax + rightMin) / 2.0;
        }
    }
    return 0.0;
}

07基准测试

最后我们来对6种解法进行一组基准对比测试,每个方法分三组测试,三组分别表示数组大小为100,1000,10000,并且生成的两个数组中会随机控制为99或100、999或1000、9999或10000,即使得奇偶随机,同时对每一组随机生成10000组测试数据,进行对比测试。测试结果如下。

在这里插入图片描述

通过测试可以发现性能如下:

解法六双指针+二分查找平分法 > 解法四双指针+二分排除法 > 解法五双指针+二分查找第K小数法 > 解法三双指针+直接取值法 > 解法二双指针+合并数组法 > 解法一娱乐法(内置方法),二分查找第K小数法比二分排除法要差一些也正常,毕竟它调用了两次算法。

测试方法代码以及示例源码都已经上传至代码库**,有兴趣的可以看看。**https://gitee.com/hugogoos/Planner

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

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

相关文章

MATLAB 从 R2024B 开始支持树莓派 5

树莓派&#xff08;Raspberry Pi&#xff09;系列是一系列基于单板计算机的微型电脑&#xff0c;由英国的树莓派基金会于 2012 年开始发布。它的目标是提供一个低成本、易于学习和玩耍的平台&#xff0c;用于教育和初学者学习计算机科学和编程。 目前市面上&#xff0c;最新最…

工地高空安全防护装备检测系统源码分享

工地高空安全防护装备检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of …

Linux驱动.之中断系统,裸机下设置寄存器,绑定中断处理函数hander(二)

17.3.3通用中断驱动文件编写 在start.S文件中我们在中断服务函数IRQ_Handler中调用了C函数system_irqhandler来处理具体的中断。此函数有一个参数&#xff0c;参数是中断号&#xff0c;但是函数system_irqhandler的具体内容还没有实现&#xff0c;所以需要实现函数system_irqha…

大屏可视化适配方案

大屏适配方案 方案一&#xff1a;rem 单位 动态设置 html 的 font-size&#xff1b;方案二&#xff1a;vw 单位&#xff1b;方案三&#xff1a;scale 等比例缩放 对比 方案一 核心思想&#xff1a;动态调整 HTML 根字体大小和 body 字体大小&#xff0c;结合百分比或vw/vh单…

配置cobbler服务提供centos7安装源

cobbler服务和pxe服务的作用类似但是从安装部署角度来说比pxe简单得多。 cobbler可以用来快速建立Linux网络安装环境&#xff0c;大幅度降低了Linux网络安装的技术门槛。 一.cobbler介绍 由RedHat公司基于python语言开发&#xff0c; 作用&#xff1a;快速批量部署Linux系统…

艾里斑(Airy Disk)与瑞利判据(Rayleigh criterion)

艾里斑的定义与形成 艾里斑&#xff08;Airy Disk&#xff09;&#xff0c;又称艾里图样&#xff08;Airy Pattern&#xff09;&#xff0c;是光通过圆形孔径发生衍射时&#xff0c;在焦点处形成的光斑。这一现象由英国数学家和天文学家George Biddell Airy在19世纪首次详细描…

面试官问:你在团队中的角色是什么?

面试官问你在团队中的角色是什么&#xff0c;其目的是了解你如何在团队环境中工作&#xff0c;以及你如何看待自己在团队中的定位。他们希望听到你如何与他人协作、你的领导能力或团队合作精神&#xff0c;以及你是否能适应不同的团队角色。 回答这类问题时&#xff0c;你可以…

shiro漏洞复现

目录 shiro介绍框架介绍判断是否使用shiro框架 环境搭建CVE-2010-3863漏洞原理影响版本漏洞复现 CVE-2016-4437漏洞原理影响版本漏洞复现 CVE-2020-1957漏洞原理影响版本漏洞复现 shiro-721拉取环境漏洞原理漏洞复现 shiro介绍 框架介绍 Apache Shiro提供了认证、授权、加密和…

关于手机号码数理的计算和推导,获得“平替”尾号

文章背景&#xff1a;我个人比较相信命运&#xff0c;对于手机号码的吉凶或是数理颇为在乎。最近&#xff0c;我沉浸于这方面的研究中&#xff0c;并发现许多手机号码的数理核心竟出奇地一致——关键在于后四位尾号&#xff0c;而前面的数字似乎并不占据主导位置。然而&#xf…

电气自动化入门01:电工基础

视频链接&#xff1a;1.1 电工知识&#xff1a;电工基础_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1PJ41117PW?p2&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.电能和电力系统 2.电工常用物理量及其应用 2.1电阻&#xff1a; 2.2电流&#xff1a; 2.3电压&…

队列-------

队列总览 队列的定义 队列的基本操作 队列回顾 顺序队列总览 队列的顺序实现 队列的初始化 入队操作&#xff0c;rear&#xff0c;后面的&#xff0c;下一个队列元素要插入的位置。front&#xff0c;前面的&#xff0c;当前队列的第一个元素。 循环队列入队操作 循环…

建设世界一流财务管理体系【数字化顶层设计】【持续更新】

财务管理是企业管理的中心环节&#xff0c;是企业实现基业长青的重要基础和保障。近年来&#xff0c;中央企业认真贯彻落实党中央、国务院决策部署&#xff0c;高度重视财务管理工作&#xff0c;持续优化管理手段&#xff0c;不断创新管理模式&#xff0c;积极应用先进管理工具…

photozoom pro 9如何激活解锁 2024最新激活解锁代码

您好,现在程程来为大家解答以上的问题。photozoom pro 9解锁代码&#xff0c;photozoom pro 9解锁代码相信很多小伙伴还不知道,现在让我们一起来看... 您好,现在程程来为大家解答以上的问题。photozoom pro 9解锁代码&#xff0c;photozoom pro 9解锁代码相信很多小伙伴还不知道…

新160个crackme - 057-bbbs-crackme04

运行分析 因软件版本老旧&#xff0c;需使用windows XP虚拟机运行有个SystemID&#xff0c;值为12345678需破解User ID和Password PE分析 yC壳&#xff0c;32位 OD手动脱壳 使用windows XP虚拟机&#xff0c;将程序拖入OD按一下F8&#xff0c;ESP变红&#xff0c;根据ESP定律设…

【网络原理】Tcp 常用提升效率机制——滑动窗口,快速重传,流量控制, 拥塞控制, 建议收藏 !!!

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

2024永久激活版 Studio One 6 Pro for mac 音乐创作编辑软件 完美兼容

Studio One 6是一款功能强大的音乐制作软件&#xff0c;由PreSonus公司开发。它提供了全面的音频录制、编辑、混音和母带处理工具&#xff0c;适用于音乐制作人、音频工程师和创作人员。 Studio One 6拥有直观的用户界面&#xff0c;使用户能够快速而流畅地进行音乐创作。它采…

MOE论文汇总

最近对MOE感兴趣&#xff0c;现汇总MOE无监督相关的文章&#xff0c;文章均为略读 文章 Self-Supervised Mixture-of-Experts by Uncertainty estimation 发表于:AAAI19 流程: 总体来说&#xff0c;当环境给出反馈&#xff0c;通过门控网络激活相应的专家去交涉。但是&…

LabVIEW减速机加载控制系统

为了保障减速机的产品质量&#xff0c;开发了一套基于LabVIEW的减速机加载控制系统。该系统利用先进的传感技术与自动化控制理念&#xff0c;实现了减速机性能的全面测试与分析&#xff0c;有效提升了生产线的自动化水平与检测效率。 项目背景 随着工业自动化水平的不断提高&a…

面试官问:请描述一次你成功解决问题的经历?

面试官为什么要这么问&#xff1f; 面试官问你描述一次成功解决问题的经历&#xff0c;主要是为了评估你的几个关键方面&#xff1a; 问题解决能力&#xff1a;了解你在面对挑战时的思维方式和应对策略。 决策能力&#xff1a;考察你在压力下做出明智决定的能力。 沟通技巧&am…

Linux实操笔记2 Ubuntu安装Nginx的不同方法

今天来了解Ubuntu或者说Linux系统安装Nginx的几种办法。包括从Ubuntu的库安装到官方源码编译安装。 一、Nginx是什么&#xff1f; 以下是来自Nginx中文文档的内容。 Nginx 是一个高性能的 Web 和反向代理服务器, 它具有有很多非常优越的特性: 作为 Web 服务器&#xff1a;相比…