• 【LeetCode】寻找两个正序数组的中位数 [H](二分法)


    4. 寻找两个正序数组的中位数 - 力扣(LeetCode)

    一、题目

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

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

    示例 1:
    输入:nums1 = [1,3], nums2 = [2]
    输出:2.00000
    解释:合并数组 = [1,2,3] ,中位数 2

    示例 2:
    输入:nums1 = [1,2], nums2 = [3,4]
    输出:2.50000
    解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

    提示:

    • nums1.length == m
    • nums2.length == n
    • 0 <= m <= 1000
    • 0 <= n <= 1000
    • 1 <= m + n <= 2000
    • -106 <= nums1[i], nums2[i] <= 106

    二、代码

    1. class Solution {
    2. public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    3. // 记录两个数组的总长度
    4. int size = nums1.length + nums2.length;
    5. // 标记总长度是奇数还是偶数
    6. boolean isEven = (size & 1) == 1 ? false : true;
    7. // 如果两个数组都不为空
    8. if (nums1 != null && nums1.length > 0 && nums2 != null && nums2.length > 0) {
    9. // 总长度的中间位置
    10. int mid = size / 2;
    11. // 如果总长度是偶数,那么要返回的中位数就是上中位数和下中位数相加除以2(两个数组排序合并之后的上中位数和下中位数)
    12. if (isEven) {
    13. // 求第mid小和第mid+1小这两个数,然后相加除以2
    14. return (findKthNum(nums1, nums2, mid) + findKthNum(nums1, nums2, mid + 1)) / 2.0;
    15. // 如果总长度是奇数,那么要返回的中位数就是第mid个数
    16. } else {
    17. // 求第mid+1小这个数
    18. return findKthNum(nums1, nums2, mid + 1);
    19. }
    20. // 只有nums2不为空
    21. } else if (nums1 == null || nums1.length == 0) {
    22. // 如果nums2长度为偶数
    23. if (isEven) {
    24. // 因为nums2本身就是有序的,直接求其中位数即可
    25. // 注意位移运算符的优先级比较弱,所以它的计算要括上括号
    26. int mid = (nums2.length >> 1) - 1;
    27. return (nums2[mid] + nums2[mid + 1]) / 2.0;
    28. // 如果nums2长度为奇数
    29. } else {
    30. // 可直接算中位数
    31. int mid = nums2.length >> 1;
    32. return nums2[mid];
    33. }
    34. // 只有nums1不为空 同上一个分支
    35. } else if (nums2 == null || nums2.length == 0) {
    36. if (isEven) {
    37. int mid = (nums1.length >> 1) - 1;
    38. return (nums1[mid] + nums1[mid + 1]) / 2.0;
    39. } else {
    40. int mid = nums1.length >> 1;
    41. return nums1[mid];
    42. }
    43. // 这个分支其实没用,只是为了有个返回值而已,避免报错
    44. } else {
    45. return 0;
    46. }
    47. }
    48. // 在两个都有序的数组中,找整体第kth小的数,两个数组不等长
    49. // 可以做到O(log(Min(M,N)))
    50. // 注意这里传入的kth是第kth小,并不是下标
    51. public int findKthNum(int[] nums1, int[] nums2, int kth) {
    52. // 标记处短数组和长数组 注意这里要有一个写上等号,保证shorts和longs数组一定指向的是不同的数组
    53. int[] shorts = nums1.length <= nums2.length ? nums1 : nums2;
    54. int[] longs = nums1.length > nums2.length ? nums1 : nums2;
    55. int shortsLen = shorts.length;
    56. int longsLen = longs.length;
    57. // 如果kth小于shortsLen
    58. if (kth <= shortsLen) {
    59. // 那么在长数组中切除和短数组等长的前缀树组,两个等长数组求上中位数,就是答案。
    60. // 传入的两个数组长度相等,直接调用算法原型
    61. return getUpMedian(shorts, 0, kth - 1, longs, 0, kth - 1);
    62. // 如果kth大于shortsLen小于等于longsLen
    63. } else if (kth > shortsLen && kth <= longsLen) {
    64. // 先找到要排除长数组哪些位置的数
    65. int preIndex = kth - shortsLen - 1;
    66. int posIndex = kth - 1;
    67. // 单独判断longs[preIndex]和shorts[shortsLen - 1]
    68. if (longs[preIndex] >= shorts[shortsLen - 1]) {
    69. return longs[preIndex];
    70. // 如果不满足上面的条件,就说明longs[preIndex]不可能是第kth小的数,直接抛弃,那么传入的两个数组长度就又相等了,直接调用算法原型
    71. } else {
    72. return getUpMedian(shorts, 0, shortsLen - 1, longs, preIndex + 1, posIndex);
    73. }
    74. // 如果kth大于longsLen
    75. } else {
    76. // 先找到段数组和长数组要排除的位置
    77. int longIndex = kth - shortsLen - 1;
    78. int shortIndex = kth - longsLen - 1;
    79. // 单独判断longs[longIndex]和shorts[shortsLen - 1]
    80. if (longs[longIndex] >= shorts[shortsLen - 1]) {
    81. return longs[longIndex];
    82. // 单独判断shorts[shortIndex]和longs[longsLen - 1]
    83. } else if (shorts[shortIndex] >= longs[longsLen - 1]) {
    84. return shorts[shortIndex];
    85. }
    86. // 舍弃掉一个不可能的数之后,传入的两个数组长度就相等了,调算法原型
    87. return getUpMedian(shorts, shortIndex + 1, shortsLen - 1, longs, longIndex + 1, longsLen - 1);
    88. }
    89. }
    90. // nums1[s1...e1]
    91. // nums2[s2...e2]
    92. // 一定等长!
    93. // 返回整体的,上中位数
    94. public int getUpMedian(int[] nums1, int s1, int e1, int[] nums2, int s2, int e2) {
    95. // 整个代码很好理解,可以在纸上列一个具体的例子,就很容易写出这个代码
    96. while (s1 < e1) {
    97. int mid1 = (s1 + e1) >> 1;
    98. int mid2 = (s2 + e2) >> 1;
    99. if (nums1[mid1] == nums2[mid2]) {
    100. return nums1[mid1];
    101. }
    102. // 执行到这里说明两个中点一定不等!
    103. // 奇数长度
    104. if ((e1 - s1 + 1) % 2 == 1) {
    105. if (nums1[mid1] > nums2[mid2]) {
    106. if (nums2[mid2] >= nums1[mid1 - 1]) {
    107. return nums2[mid2];
    108. }
    109. e1 = mid1 - 1;
    110. s2 = mid2 + 1;
    111. } else {
    112. if (nums2[mid2 - 1] <= nums1[mid1]) {
    113. return nums1[mid1];
    114. }
    115. e2 = mid2 - 1;
    116. s1 = mid1 + 1;
    117. }
    118. // 偶数长度
    119. } else {
    120. if (nums1[mid1] > nums2[mid2]) {
    121. e1 = mid1;
    122. s2 = mid2 + 1;
    123. } else {
    124. e2 = mid2;
    125. s1 = mid1 + 1;
    126. }
    127. }
    128. }
    129. // 跳出上面循环后,只剩下两个数了,小的那个数就是上中位数。我感觉上面这个过程就和二分有点类似
    130. return Math.min(nums1[s1], nums2[s2]);
    131. }
    132. }

    三、解题思路 

    一看到有序,就要想到利用二分的思路,将数据规模减半,基于一个算法模型可以求出任意第k个数组,既然也就可以求中位数。整个时间复杂度是O(log(Min(M,N)))。

  • 相关阅读:
    Session会话追踪的实现机制
    spark内置数据类型
    在线客服系统品牌排行榜
    050:vue+openlayers使用Popup组件显示经纬度坐标(代码示例)
    SSRF漏洞
    神经网络一般训练多少次,神经网络训练时间过长
    详解SpringBoot的核心特性
    ComText让机器人有了情节记忆
    安装docker ,更换docker版本
    Xilinx SDK编译完成自动生成SREC文件(适用于ISE、Vivado、Vitis)
  • 原文地址:https://blog.csdn.net/cy973071263/article/details/127593593