给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
第四题,这次难度是困难,这是我们第一次遇到困难。
我们遇到什么困难也不要怕,微笑着面对它! 消除恐惧的最好办法就是面对恐惧! 加油!奥利给!
呃,串戏了
回正题,老规矩先看题干构建函数头,给定两个大小为 m 和 n 的有序数组 nums1 和 nums2,输入为两个有序数组即 int[] nums1 和 int[] nums2 。请你找出这两个有序数组的中位数,那返回值是一个 double (因为中位数肯能是两个数的平均值,所以会有小数位)。
public double FindMedianSortedArrays(int[] nums1, int[] nums2)
再接着读题,要求算法的时间复杂度为 O(log(m + n))。这里问题就来了,求两个数组的中位数不难,只要将两个数组合并,即可很容易的找出其中位数,但是要求时间复杂度是 O(log(m + n)) 又是什么鬼。时间复杂度我们知道是标识算法运算n时常与输入变量的长度相关性的标志,常见的包括 O(1)常数时间 O(n)线性时间 O(logn)对数时间 等。
题目要求我们时间复杂度为 O(log(m+n))就是要求算法为对数时间,而常见的对数时间的算法有,二分查找和一些二叉树的操作。这就是直接给了我们答案有木有。看来是要对数据进行一个二分查找的改造了(这里肯定不是二叉树,因为构建二叉树的算法复杂度已经是O(m+n)了)。
我们来看二分查找算法的百度百科定义,二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
嗯,顺序存储结构,咱们的两个数组都是有序的,这很好,二分查找的过程即为查找中间值,比较大小,如相等则是要找的值,如不相等再去相应的子表中再次进二分查找。这里我给出百度百科中的C#二分查找源码。
public static int Method(int[] nums, int low, int high, int target)
{while (low <= high){int middle = (low + high) / 2;if (target == nums[middle]){return middle;}else if (target > nums[middle]){low = middle + 1;}else if (target < nums[middle]){high = middle - 1;}}return -1;
}
那么我们需要找的是什么,中位数,再次引用百度百科定义中位数(Median)又称中值,统计学中的专有名词,是按顺序排列的一组数据中居于中间位置的数,代表一个样本、种群或概率分布中的一个数值,其可将数值集合划分为相等的上下两部分。对于有限的数集,可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数。 所以我们要找的不是一个特定的值,而是一个存在于特定位置的数。所以我们只要找到特定位置上数是多少就好了。
首先我们有两条数组,假设 我们合并两条数组 nums1 nums2 = nums,那么位于 a 位置的中位数 将其分为两部分即nums[0~(a-1)] 与 nums[a~(nums.Lenght-1)] ,且s两部分的长度相同。我们设这两部分为numsLeft 与 numsRight,那么numsLeft中应包含 nums1的一部分与nums2的一部分,我们可以认为 numsLeft = nums1[0~(i-1)] + nums2[0~(j-1)],那么 numsRight = nums1[i~(nums1.Lenght-1)] + nums2[j~(nums2.Lenght-1)],这个是我们要达成的目标。并且我们可以知道 Max(numsLeft)<=Min(numsRight) ,这便是我们的判断标准。
那么看下具体的代码实现吧
public double FindMedianSortedArrays(int[] nums1, int[] nums2)
{if (nums1.Length > nums2.Length)//主操作放在长的数组上可以减少大多数情况下的算法耗时{ int[] temp = nums1;nums1 = nums2;nums2 = temp;}int minI = 0; //i 所处位置可能的最小值int maxI = nums1.Length; //i 所处位置可能的最大值int halfLen = (nums1.Length + nums2.Length + 1) / 2;//i的一半长度 由于int计算小数部分会被完全舍去,所以+1之后再除可以适应奇偶问题while (minI <= maxI){int i = (minI + maxI) / 2; //在可能的范围内用二分查找法来找到正确的数值,这个是中间值int j = halfLen - i; //找到nums2 放入numsLeft中的最大值的位置if (i < maxI && nums2[j - 1] > nums1[i]) //判断nums2 放入 numsLeft 中的最大值是否大于 nums1 放入numsRight 中的最小值{minI = i + 1; // i 这个中间值小了,我们调整 i 的范围}else if (i > minI && nums1[i - 1] > nums2[j])//判断nums2 放入 numsRight 中的最小值是否小于 nums1 放入numsLeft 中的最大值{maxI = i - 1; // i 这个中间值大了,我们调整 i 的范围}else //i值是正确的值{int maxLeft; //找到左侧最大值if (i == 0){maxLeft = nums2[j - 1]; }else if (j == 0) {maxLeft = nums1[i - 1]; }else {maxLeft = Math.Max(nums1[i - 1], nums2[j - 1]); }if ((nums1.Length + nums2.Length) % 2 == 1) { return maxLeft; }
int minRight; //找到右侧最小值if (i == nums1.Length) {minRight = nums2[j]; }else if (j == nums2.Length) {minRight = nums1[i]; }else { minRight = Math.Min(nums2[j], nums1[i]); }
return (maxLeft + minRight) / 2.0;}}return 0.0;
}
跑一下
执行用时 :128 ms, 在所有 C# 提交中击败了99.21%的用户
内存消耗 :26.9 MB, 在所有 C# 提交中击败了5.05%的用户
效率不错。这个代码我原来自己写的比较乱,讲起来比较麻烦我又参考了LeetCode官方给出的答案进行了少许的修改。我们学习就是这样,自己不会的有好的指导就应该去学,学习永远是最值得骄傲的。
好了,这是我第一次讲解困难难度的题目,有不清楚的地方大家可以在留言里提出,我会尽量给大家回复并讲解清楚的。那么就让我们一起努力吧!