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

题目:给定两个大小分别为 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中查找Kif (idx1 >= nums1.Length){//因为数组1没有数据了,所以数组2索引前进到K数索引处idx2 += kth - 1;return nums2[idx2];}//数组2没数据,则直接在数组1中查找Kif (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,则左边最大值直接取数组2if (idx1 == 0){leftMax = nums2[idx2 - 1];}//如果分隔到数组2的最左边即数组2当前索引为0,则左边最大值直接取数组1else 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索引为其自身长度,则右边最小值直接取数组2if (idx1 == nums1.Length){rightMin = nums2[idx2];}//如果分隔到数组最右边即数组2索引为其自身长度,则右边最小值直接取数组1else 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.xdnf.cn/news/1536783.html

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

相关文章

Linux常用命令以及操作技巧

&#x1f30f;个人博客主页&#xff1a;意疏-CSDN博客 希望文章能够给到初学的你一些启发&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏支持一下笔者吧&#xff5e; 阅读指南&#xff1a; 开篇说明帮助命令常见的七个linux操作终端实用的技巧跟文件目录…

idea集成和使用Git指南

前言 Git是一个分布式的版本控制工具&#xff0c;可以管理我们开发过程中的源代码文件&#xff0c;而idea是Java的集成开发环境&#xff0c;在idea中配置Git&#xff0c;可以提高我们的团队开发效率。因此在idea中集成Git并使用Git管理我们的源代码是必要的 第一步&#xff1a;…

计算机毕业设计python校园物资招标投标竞标系统 w235g

目录 技术栈和环境说明具体实现截图预期达到的目标系统设计详细视频演示技术路线解决的思路性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示研究方法感恩大学老师和同学源码获取 技术栈和环境说明 本系统以Python开发语言开发&am…

Java 入门指南:JVM(Java虚拟机)垃圾回收机制 —— 死亡对象判断方法

文章目录 垃圾回收机制死亡对象判断方法引用计数法可达性分析算法可以作为 GC Roots 的对象判断对象被回收需要经历的过程 引用类型引用汇总引用队列 废弃常量判定废弃常量废弃原因遵循原则 无用的类所需条件造成的问题解决步骤 垃圾回收机制 垃圾回收&#xff08;Garbage Col…

【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)

目录 一、插入排序 算法思想 二、插入排序 算法步骤 四、复杂度分析 时间复杂度&#xff1a;O(n^2) 空间复杂度&#xff1a;O(1) 稳定性&#xff1a;稳定算法 五、应用场景 &#x1f493; 博客主页&#xff1a;C-SDN花园GGbond ⏩ 文章专栏&#xff1a;探索数据结构…

STM32之FMC—扩展外部 SDRAM

文章目录 一、FMC外设介绍二、SDRAM 控制原理1、SDRAM关键参数a、容量、分区b、引脚SDRAM 使用 2、SDRAM芯片IS42S16400J3、SDRAM 控制引脚说明控制逻辑地址控制SDRAM 的存储阵列SDRAM 的命令预充电刷新 W9825G6KH&#xff1a;W9825G6KH引脚 三、STM32F429 FMC四、其他文章打开…

基于ssm的个性化影片推荐系统设计与实现

需要项目源码请联系我&#xff0c;目前有各类成品 毕设 javaweb ssh ssm springboot等等项目框架&#xff0c;源码丰富。 专业团队&#xff0c;咨询就送开题报告&#xff0c;活动限时免费&#xff0c;有需要的朋友可以来咨询。 一、摘要 随着科学技术的飞速发展&#xff0c;社…

Matlab simulink建模与仿真 第十五章(信号源库)

参考视频&#xff1a;simulink1.1simulink简介_哔哩哔哩_bilibili 一、信号源库中的模块概览 注&#xff1a;部分模块在第二章中有介绍&#xff0c;本章不再赘述。 二、from输入源模块 1、From Workspace模块 &#xff08;1&#xff09;该模块可从MATLAB工作区、模型工作区…

双指针算法专题(2)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; 优选算法专题 想要了解双指针算法的介绍&#xff0c;可以去看下面的博客&#xff1a;双指针算法的介绍 目录 611.有效三角形的个数 LCR 1…

King3399 SDK编译简明教程

该文章仅供参考&#xff0c;编写人不对任何实验设备、人员及测量结果负责&#xff01;&#xff01;&#xff01; 0 引言 文章主要介绍King3399&#xff08;瑞芯微rk3399开发板&#xff0c;荣品&#xff09;官方SDK编译过程&#xff0c;涉及环境配置、补丁以及编译过程中注意事…

shiro漏洞复现

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

CSARA机械手正反解代码解读和左右手定则应用

前言&#xff1a;前段时间在某鱼上买了一份CSARA的机械臂的程序&#xff0c;拿出来分享一下&#xff0c;并记录一下。说明一下并非是公司的核心代码&#xff0c;我也不搞这个....侵权就删了。 首先简单回顾一下CSARA的正逆解。 根据几何的方法能求出末端在平面坐标系中的xy坐标…

第二百三十五节 JPA教程 - JPA Lob列示例

JPA教程 - JPA Lob列示例 以下代码显示了如何使用Lob注释将字节数组保存到数据库。 LOB在数据库中有两种类型&#xff1a;字符大对象&#xff08;称为CLOB&#xff09;和二进制大对象&#xff08;或BLOB&#xff09;。 CLOB列保存大字符序列&#xff0c;BLOB列可存储大字节序…

Linux memcg lru lock提升锁性能

内核关于per memcg lru lock的重要提交&#xff1a; f9b1038ebccad354256cf84749cbc321b5347497 6168d0da2b479ce25a4647de194045de1bdd1f1d 计算虚拟地址转换基本机制 为了处理多应用程序的地址冲突&#xff0c; linux 系统在应用中使用了虚拟地址&#xff0c;得益于硬件的…

SpringBoot+vue集成sm国密加密解密

文章目录 前言认识SM2后端工具类实现引入依赖代码实现工具类&#xff1a;SM2Util 单元测试案例1&#xff1a;生成服务端公钥、私钥&#xff0c;前端js公钥、私钥案例2&#xff1a;客户端加密&#xff0c;服务端完成解密案例3&#xff1a;服务端进行加密&#xff08;可用于后面前…

禹神:一小时彻底搞懂跨域解决方案

1. 浏览器的同源策略 2. 跨域会受到哪些限制 4. CORS 解决 Ajax 跨域问题 exposedHeaders 不加这个&#xff0c;js拿不到这个响应头(浏览器控制台network中能看见&#xff0c;但是js拿不到) 5. JSONP 解决跨域问题 JSOP只能解决get请求 服务端代码 客户端代码 服务端代码升…

卡尔曼滤波中Q和R与噪声的关系

卡尔曼滤波 一种用于估计系统状态的递归滤波器&#xff0c;通过融合传感器测量和系统模型&#xff0c;提供系统状态的最优估计。 Q和R是什么 在卡尔曼滤波中&#xff0c;Q和R分别表示过程噪声和测量噪声的协方差矩阵。 Q Q Q矩阵&#xff08;过程噪声协方差矩阵&#xff09;…

LC并联电路在正弦稳态下的传递函数推导(LC并联谐振选频电路)

LC并联电路在正弦稳态下的传递函数推导&#xff08;LC并联谐振选频电路&#xff09; 本文通过 1.解微分方程、2.阻抗模型两种方法推导 LC 并联选频电路在正弦稳态条件下的传递函数&#xff0c;并通过仿真验证不同频率时 vo(t) 与 vi(t) 的幅值相角的关系。 电路介绍 已知条件…

人工智能和大模型的简介

文章目录 前言一、大模型简介二、大模型主要功能1、自然语言理解和生成2、文本总结和翻译3、文本分类和信息检索4、多模态处理三、大模型的技术特性1、深度学习架构2、大规模预训练3、自适应能力前言 随着技术的进步,人工智能(Artificial Intelligence, AI)和机器学习(Mac…

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

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