问题:

There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays.
The overall run time complexity should be O(log (m+n)).
Example1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5

官方难度:

Hard

翻译:

两个已排序数组,其长度分别是m和n,找出这两个数组的中位数。

要求整个算法的时间复杂度是O(log(m+n))。

例子:

[1,3]和[2],中位数是2.0

[1,2]和[3,4],中位数是2.5

方法一:

  1. 将两个有序数组,合并成一个有序数组,取中位数。
  2. 创建一个新的数组,长度为m+n。
  3. 用两个指针记录数组当前位置的索引,每次比较这两个的值,将较小的那个值加入新数组,这个指针后移。
  4. 每次循环开始时,有限判断是否有指针跑到底,之后只迁移另一个数组的值。
  5. 时间复杂度是O(m+n)。

方法一的解题代码:

  1. // 将两个数组合并成新数组取中位数,时间复杂度是O(m+n)
  2. private static double method1(int[] array1, int[] array2) {
  3. final int LENGTH = array1.length + array2.length;
  4. int[] resultArray = new int[LENGTH];
  5. // 数组当前位置索引
  6. int index1 = 0;
  7. int index2 = 0;
  8. int[] tempArray = null;
  9. int tempIndex = 0;
  10. // 合并数组赋值
  11. for (int i = 0; i < LENGTH; i++) {
  12. if (tempArray == null) {
  13. // 优先判断其中一个数组已经被掏空的情况
  14. // 特别注意,掏空的时候index会比最后的index+1
  15. if (index1 == array1.length || index2 == array2.length) {
  16. // temp赋值+本次赋值
  17. if (index1 == array1.length) {
  18. tempArray = array2;
  19. tempIndex = index2;
  20. resultArray[i] = tempArray[tempIndex++];
  21. } else {
  22. tempArray = array1;
  23. tempIndex = index1;
  24. resultArray[i] = tempArray[tempIndex++];
  25. }
  26. // 这个continue是必要的,赋值之后,直接下一次循环
  27. continue;
  28. }
  29. // 两个数组都有剩余的情况
  30. if (array1[index1] < array2[index2]) {
  31. resultArray[i] = array1[index1++];
  32. } else {
  33. resultArray[i] = array2[index2++];
  34. }
  35. } else {
  36. // 操作tempArray
  37. resultArray[i] = tempArray[tempIndex++];
  38. }
  39. }
  40. // 分奇偶确定中位数,除数要特别标注2.0,不然以整数规则计算
  41. if (LENGTH % 2 == 0) {
  42. return (resultArray[LENGTH / 2] + resultArray[LENGTH / 2 - 1]) / 2.0;
  43. }
  44. return resultArray[(LENGTH - 1) / 2];
  45. }

method1

方法二:

  1. 从方法一中可以看出,至少做了一半的无谓操作。当指针跑到中位数的时候,其实可以直接计算出来,然后break。甚至于,不需要额外的空间去纪录新的数组。
  2. 定义2个目标索引,记录中位数的位置,这里可以实现奇偶情况统一,分别是(LENGTH-1)/2和LENGTH/2,在指针跑到LENGTH/2的时候break,将累加的值除以2就是中位数的值。
  3. 定义两个引用,当前操作数组的引用currentArray和当前数组的索引currentIndex,之后两个指针的自增和currentIndex没有关系,用这两个引用去操作中位数的累加赋值。
  4. 同样需要考虑,在指针跑到一个数组结束之后,仍然没有跑到中位数相关数字的情况,这时候,可以在剩余数组中直接计算中位数的值。
  5. 有一种最特殊的情况,总长度是偶数,一个数组跑完之后,记录了中位数的第一个值,针对这种情况,使用一个标志位,在第一次中位数赋值之后改为true,然后与剩余数组的第一个值相加除以2.0就是中位数的值。
  6. 时间复杂度是方法一的一半O((m+n)/2),仍然未达到要求。

方法二的解题代码:

  1. // 不需要重组整个数组,只需要重组一半,时间复杂度是O((m+n)/2)
  2. private static double method2(int[] array1, int[] array2) {
  3. final int LENGTH = array1.length + array2.length;
  4. // 中位数的索引
  5. int targetIndex1 = (LENGTH - 1) / 2;
  6. int targetIndex2 = LENGTH / 2;
  7. // 结果
  8. int result = 0;
  9. boolean flag = false;
  10. // 数组当前位置索引
  11. int index1 = 0;
  12. int index2 = 0;
  13. // 当前操作数组及索引
  14. int[] currentArray;
  15. int currentIndex;
  16. // 开启循环
  17. for (int i = 0; i < LENGTH; i++) {
  18. // 某一数组遍历结束
  19. if (index1 == array1.length || index2 == array2.length) {
  20. int[] remain, exhaust;
  21. if (index1 == array1.length) {
  22. remain = array2;
  23. exhaust = array1;
  24. } else {
  25. remain = array1;
  26. exhaust = array2;
  27. }
  28. if (!flag) {
  29. // 中位数在之后,可以直接计算
  30. return (remain[(LENGTH - 1) / 2 - exhaust.length] + remain[LENGTH / 2 - exhaust.length]) / 2.0;
  31. } else {
  32. // 偶数位的总长度,且result已经加过一次
  33. return (result + remain[i - exhaust.length]) / 2.0;
  34. }
  35. }
  36. // 索引自增,当前操作数组及索引赋值
  37. if (array1[index1] < array2[index2]) {
  38. currentArray = array1;
  39. currentIndex = index1;
  40. index1++;
  41. } else {
  42. currentArray = array2;
  43. currentIndex = index2;
  44. index2++;
  45. }
  46. // 结果赋值,奇偶长度统一
  47. if (i == targetIndex1) {
  48. flag = true;
  49. result += currentArray[currentIndex];
  50. }
  51. if (i == targetIndex2) {
  52. result += currentArray[currentIndex];
  53. break;
  54. }
  55. }
  56. return result / 2.0;
  57. }

method2

方法三:

  1. 可以借鉴二分查找法的思想:比较两个数组的中间值,去掉中间值较小数组的前面部分,和中间值较大数组的后面部分,形成两个新的数组进行递归。
  2. 这种方法需要考虑很多特殊情况,极易出错,但是确实具有O(log(m+n))的时间复杂度。
  3. 首先每一次两个数组去掉的部分,长度必须相同,保证最终的中位数不会改变,如果长度不一,去较小的那个值去掉。
  4. 当一个数组的长度为0时,直接计算另一个数组的中位数。
  5. 在保证两个数组的长度均不为0,这意味着,两个数组都有了“中间值”这个概念。考虑两个数组的中间值相等的情况,根据两个数组长度的奇偶性,又可以分成三种情况讨论。
  6. 在中间值相等的情况下,两个数组长度均为奇数,中间值即是最后的中位数。
  7. 在中间值相等的情况下,两个数组长度均为偶数,中位数是中间值与两数组中间值之后的那一个数的较小值的平均值。
  8. 在中间值相等的情况下,两个数组长度一奇一偶,中位数就是偶数长度数组的中位数。
  9. 考虑一下,每次去掉的部分,是不是会影响最终的值?有一种特殊的情况会造成最终的中位数值变化,要特殊讨论。就是:两个偶数长度的数组,中间值较大的数组和它之后第一个值完全被另一个数组从两侧包裹,如:[2,3]和[0,1,4,5]。2>1且3<4,按照正常的流程,下一次递归的数组应该是[2]和[1,4,5],很明显结果被改变了。这种情况下直接取[2,3]数组的中位数作为最终结果即可。
  10. 如果去掉的长度为0怎么办?这时候,要考虑一下当一个数组的长度为1时,该怎么处理。首先考虑,另一个数组的长度也为1,这时候直接取平均值。
  11. 一般来说,理想的情况是,将这个长度为1的数组消掉,进入下一次循环,但是这必须是这个值并不会影响到最终中位数计算的前提下。根据另一个数组长度的奇偶性分2种情况考虑。
  12. 另一个数组长度为偶数,长度为1的数组会影响到最后结果的情况是:这个值大于另一个数组的中间值,却小于另一个数组中间值之后的那个值,典型的就是[2]和[0,1,3,4]。
  13. 另一个数组长度为奇数,长度为1的数组会影响到最后结果的情况是:这个值大于另一个数组的中间值,却小于之后的值,如[4]和[1,3,5]。或者这个值小于另一个数组的中间值,却大于之前的值,如[2]和[1,3,5]。
  14. 在没有出现类似12和13的情况下,可以没有顾忌的削去这个数组,同时另一个数组根据中间值比较的情况,选择去掉数组的第一个值还是最后一个值,进入下一次递归。
  15. 除了存在长度为1的数组,还有1种可能导致削去长度为0的情况,就是中价值较小的数组长度为2。这种情况下,中间值较小的数组去掉第一项,另一个数组去掉最后一项,进入下一次递归。
  16. 最后,记得入参检查,存在null的输入或两个数组长度和为0时,抛出异常。

方法三的解题代码:

  1. // 因为两数组分别有序,用类二分查找法寻找中位数,时间复杂度O(log(m+n))
  2. public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
  3. // 入参校验
  4. if (nums1 == null || nums2 == null || nums1.length + nums2.length == 0) {
  5. throw new IllegalArgumentException("Input error");
  6. }
  7. // 特殊情况:一个数组长度为0
  8. if (nums1.length == 0 || nums2.length == 0) {
  9. int[] array = nums1.length == 0 ? nums2 : nums1;
  10. return (array[array.length / 2] + array[(array.length - 1) / 2]) / 2.0;
  11. }
  12. // 中位数
  13. int median1 = nums1[(nums1.length - 1) / 2];
  14. int median2 = nums2[(nums2.length - 1) / 2];
  15. // 特殊情况:两个数组的中位数相同
  16. if (median1 == median2) {
  17. // 两数组长度为奇数
  18. if (nums1.length % 2 == 1 && nums2.length % 2 == 1) {
  19. return median1;
  20. }
  21. // 两数组长度为偶数
  22. if (nums1.length % 2 == 0 && nums2.length % 2 == 0) {
  23. return (median1 + Math.min(nums1[nums1.length / 2], nums2[nums2.length / 2])) / 2.0;
  24. }
  25. // 一奇一偶,就是偶数长度数组的中位数
  26. int[] array = nums1.length % 2 == 0 ? nums1 : nums2;
  27. return (median1 + array[array.length / 2]) / 2.0;
  28. }
  29. // 特殊情况:一个数组长度为1
  30. if (nums1.length == 1 || nums2.length == 1) {
  31. int[] array1, array2;
  32. if (nums1.length == 1) {
  33. array1 = nums1;
  34. array2 = nums2;
  35. } else {
  36. array1 = nums2;
  37. array2 = nums1;
  38. }
  39. // 特殊情况:另一个数组的长度也为1
  40. if (array2.length == 1) {
  41. return (array1[0] + array2[0]) / 2.0;
  42. }
  43. // 特殊情况:两个数组的中位数计算,包含长度为1的数组的值
  44. if (array2.length % 2 == 0) {
  45. if (array1[0] >= array2[(array2.length - 1) / 2] && array1[0] <= array2[array2.length / 2]) {
  46. return array1[0];
  47. }
  48. } else {
  49. if (array1[0] < array2[array2.length / 2] && array1[0] > array2[array2.length / 2 - 1]) {
  50. return (array1[0] + array2[array2.length / 2]) / 2.0;
  51. }
  52. if (array1[0] > array2[array2.length / 2] && array1[0] < array2[array2.length / 2 + 1]) {
  53. return (array1[0] + array2[array2.length / 2]) / 2.0;
  54. }
  55. }
  56. // 将array1长度降为0,进入下一次递归
  57. if (array1[0] > array2[array2.length / 2]) {
  58. return findMedianSortedArrays(new int[0], Arrays.copyOfRange(array2, 1, array2.length));
  59. } else {
  60. return findMedianSortedArrays(new int[0], Arrays.copyOf(array2, array2.length - 1));
  61. }
  62. }
  63. // 中位数大的数组是array1
  64. int[] array1, array2;
  65. if (median1 > median2) {
  66. array1 = nums1;
  67. array2 = nums2;
  68. } else {
  69. array1 = nums2;
  70. array2 = nums1;
  71. }
  72. // 特殊情况:两个数组的中位数,就是array1的中位数
  73. // 只有在两个偶数长度的数组的情况下,才能实现
  74. if (array1.length % 2 == 0 && array2.length % 2 == 0) {
  75. if (array1[array1.length / 2] <= array2[array2.length / 2]) {
  76. return (array1[(array1.length - 1) / 2] + array1[array1.length / 2]) / 2.0;
  77. }
  78. }
  79. int reduce1 = array1.length / 2;
  80. int reduce2 = (array2.length - 1) / 2;
  81. // 特殊情况:array2的长度为2
  82. if (reduce2 == 0) {
  83. return findMedianSortedArrays(Arrays.copyOf(array1, array1.length - 1), new int[] { array2[1] });
  84. }
  85. int reduce = Math.min(reduce1, reduce2);
  86. // 削去数组递归
  87. return findMedianSortedArrays(Arrays.copyOf(array1, array1.length - reduce),
  88. Arrays.copyOfRange(array2, reduce, array2.length));
  89. }

findMedianSortedArrays

相关链接:

https://leetcode.com/problems/median-of-two-sorted-arrays/

https://github.com/Gerrard-Feng/LeetCode/blob/master/LeetCode/src/com/gerrard/algorithm/hard/Q004.java

PS:如有不正确或提高效率的方法,欢迎留言,谢谢!

No.004:Median of Two Sorted Arrays的更多相关文章

  1. LeetCode2:Median of Two Sorted Arrays

    题目: There are two sorted arrays A and B of size m and n respectively. Find the median of the two sor ...

  2. leetcode第四题:Median of Two Sorted Arrays (java)

    Median of Two Sorted Arrays There are two sorted arrays A and B of size m and n respectively. Find t ...

  3. Q4:Median of Two Sorted Arrays

    4. Median of Two Sorted Arrays 官方的链接:4. Median of Two Sorted Arrays Description : There are two sort ...

  4. LeetCode第[4]题(Java):Median of Two Sorted Arrays 标签:Array

    题目难度:hard There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median ...

  5. 【LeetCode算法题库】Day2:Median of Two Sorted Arrays & Longest Palindromic Substring & ZigZag Conversion

    [Q4] There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of th ...

  6. 4:Median of Two Sorted Arrays

    here are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two ...

  7. LeetCode第[4]题(Java):Median of Two Sorted Arrays (俩已排序数组求中位数)——HARD

    题目难度:hard There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median ...

  8. leetcode 4:Median of Two Sorted Arrays

    public double FindMedianSortedArrays(int[] nums1, int[] nums2) { int t=nums1.Length+nums2.Length; in ...

  9. LeetCode 004 Median of Two Sorted Arrays

    题目描述:Median of Two Sorted Arrays There are two sorted arrays A and B of size m and n respectively. F ...

随机推荐

  1. java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)

    一.Condition 类 在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.c ...

  2. PHP设计模式(四)单例模式(Singleton For PHP)

    今天讲单例设计模式,这种设计模式和工厂模式一样,用的非常非常多,同时单例模式比较容易的一种设计模式. 一.什么是单例设计模式 单例模式,也叫单子模式,是一种常用的软件设计模式.在应用这个模式时,单例对 ...

  3. TypeScript

    TypeScript: Angular 2 的秘密武器(译)   本文整理自Dan Wahlin在ng-conf上的talk.原视频地址: https://www.youtube.com/watch? ...

  4. Unity3D新手引导开发手记

    最近开始接手新手引导的开发,记录下这块相关的心得 首先客户端是Unity,在接手前,前面的同学已经初步完成了新手引导框架的搭建,这套框架比较简单,有优点也有缺点,稍后一一点评 我们的新手引导是由一个个 ...

  5. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  6. Lamda表达式多个字段排序问题 ThenBy、ThenByDescending

    示例代码: //ThenBy - 在 OrderBy 或 OrderByDescending 的基础上再正序排序 //ThenByDescending - 在 OrderBy 或 OrderByDes ...

  7. iOS---基于对Sqlilte3的二次包装的第三次包装--->JKDBModel ,一个好用的离线缓存库

    https://github.com/Joker-King/JKDBModel 1.将FMDB和DBModel拖入项目中,然后添加libsqlite3.dylib   2. #import " ...

  8. JavaScript随笔4

    (1) 表单:向服务器提交数据 action: 提交到哪里 表单事件: onsubmit: 提交时发生 onreset: 重置时发生(2) 运动框架: 1.在开始运动时.关闭已有定时器 2.把运动和停 ...

  9. GitHub实战系列~4.把github里面的库克隆到指定目录+日常使用 2015-12-11

    GitHub实战系列汇总:http://www.cnblogs.com/dunitian/p/5038719.html ———————————————————————————————————————— ...

  10. 纪录我的iOS学习之路

    学习资料的网址 田伟宇(Casa Taloyum)有几篇介绍iOS架构的文章,一级棒!原博客链接. iOS应用架构谈 开篇 iOS应用架构谈 view层的组织和调用方案 iOS应用架构谈 网络层设计方 ...