LeetCode题练习与总结:寻找两个正序数组的中位数

112 篇文章 0 订阅
19 篇文章 0 订阅

一、题目描述

给定两个大小分别为 mn 的正序(从小到大)数组 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
  • -10^6 <= nums1[i], nums2[i] <= 10^6

二、方法一

(一)解题思路

  1. 首先,确保 nums1 是较短的数组,这样可以保证在二分查找时,较短数组的索引不会超出范围。
  2. 初始化两个指针 leftright,以及 halfLenhalfLen 是合并后数组的中位数位置。
  3. 使用二分查找法,通过比较 nums1i 索引和 nums2j 索引的元素,来缩小搜索范围。
  4. nums1[i] 小于 nums2[j-1] 时,说明 nums1[i] 不可能是中位数,因此将 left 指针向右移动。
  5. nums1[i] 大于等于 nums2[j-1] 时,说明 nums1[i] 可能是中位数,将 right 指针向左移动。
  6. leftright 相遇时,找到了合并后数组的中位数位置。
  7. 根据中位数位置,计算 nums1nums2 中可能的中位数。
  8. 如果合并后的数组长度是奇数,返回最大的那个中位数;如果是偶数,返回两个中位数的平均值。

(二)具体代码

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 确保 nums1 是较短的数组
        if (nums1.length > nums2.length) {
            int[] temp = nums1;
            nums1 = nums2;
            nums2 = temp;
        }
        int m = nums1.length;
        int n = nums2.length;
        int total = m + n;
        boolean isEven = total % 2 == 0;

        // 初始化二分查找的左右指针和中位数位置
        int left = 0, right = m, halfLen = (total + 1) / 2;

        // 二分查找,找到合并后数组的中位数位置
        while (left < right) {
            int i = left + (right - left) / 2;
            int j = halfLen - i;
            if (i < m && nums1[i] < nums2[j - 1]) {
                left = i + 1;
            } else {
                right = i;
            }
        }

        // 计算中位数
        int i = left;
        int j = halfLen - i;
        int num1LeftMax = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1];
        int num1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i];
        int num2LeftMax = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1];
        int num2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j];

        if (isEven) {
            // 如果总长度是偶数,返回两个中位数的平均值
            return (Math.max(num1LeftMax, num2LeftMax) + Math.min(num1RightMin, num2RightMin)) / 2.0;
        } else {
            // 如果总长度是奇数,返回最大的中位数
            return Math.max(num1LeftMax, num2LeftMax);
        }
    }
}

(三)时间复杂度和空间复杂度

1. 时间复杂度
  • 时间复杂度是 O(log(min(m, n)))。
  • 这是因为代码使用了二分查找算法来确定合并后数组的中位数位置。
  • 在最坏的情况下,二分查找需要进行 log(min(m, n)) 次比较,其中 m 和 n 分别是两个数组的长度。
  • 由于我们总是比较较短数组的元素,所以时间复杂度不会超过较短数组的长度。
2. 空间复杂度
  • 空间复杂度是 O(1)。
  • 代码中没有使用任何额外的数据结构来存储数据,所有的计算都是在常数空间内完成的。
  • 变量 i, j, left, right, halfLen, num1LeftMax, num1RightMin, num2LeftMax, 和 num2RightMin 都是在常数空间内进行操作。

(四)总结知识点

  1. 数组交换:通过临时变量 temp 实现了两个数组 nums1nums2 的交换,确保较短的数组是 nums1。这有助于后续的二分查找算法,因为它减少了比较的次数。

  2. 二分查找:代码中的 while 循环实现了二分查找算法,通过不断缩小搜索范围来确定合并后数组的中位数位置。这是解决这个问题的关键步骤,因为它允许我们在 O(log(min(m, n))) 时间内找到中位数。

  3. 边界条件处理:在计算中位数时,代码正确处理了数组的边界条件,例如当 ij 为 0 或数组长度时,如何确定 num1LeftMaxnum1RightMinnum2LeftMaxnum2RightMin 的值。

  4. 数学逻辑:代码中使用了数学逻辑来确定中位数。当总长度为奇数时,中位数是较大的那个数;当总长度为偶数时,中位数是两个数的平均值。

  5. 条件判断:通过 isEven 变量来判断总长度是奇数还是偶数,然后根据这个条件返回正确的中位数。

  6. 数据类型处理:在计算平均值时,代码正确地将整数转换为浮点数,以确保结果的准确性。

  7. 代码优化:通过确保较短的数组用于二分查找,代码优化了算法的效率,减少了不必要的比较。

  8. 常数空间复杂度:整个算法没有使用额外的数据结构,所有操作都在常数空间内完成,保持了空间复杂度为 O(1)。

三、方法二

(一)解题思路

  1. 确保较短数组优先处理:首先,如果 nums1 的长度大于 nums2,交换两个数组,以确保较短的数组是 nums1。这有助于减少二分查找的迭代次数。

  2. 二分查找:使用二分查找法来确定合并后数组的中位数位置。初始化两个指针 iminimax,分别表示搜索范围的最小值和最大值,即 0mnums1 的长度)。

  3. 迭代搜索:在循环中,计算中间值 i 和对应的 jj = (m + n + 1) / 2 - i),然后比较 nums1[i]nums2[j-1] 的大小。根据比较结果,调整 iminimax 的值,缩小搜索范围。

  4. 找到中位数:当 iminimax 相遇时,找到了合并后数组的中位数位置。此时,根据 ij 的值,可以确定 nums1nums2 中的元素,这些元素可能是中位数。

  5. 计算中位数:如果合并后的数组长度是奇数,直接返回 maxLeft(即 nums1[i-1]nums2[j-1] 中的最大值)。如果长度是偶数,返回 maxLeftminRight(即 nums1[i]nums2[j] 中的最小值)的平均值。

  6. 处理边界情况:在计算 maxLeftminRight 时,需要处理数组的边界情况,例如当 ij0 或数组的末尾时,确保不会访问数组的越界位置。

  7. 返回结果:根据上述步骤,返回计算出的中位数。

(二)具体代码

class Solution {
   public double findMedianSortedArrays(int[] nums1, int[] nums2) {
       // 获取两个数组的长度
       int m = nums1.length;
       int n = nums2.length;

       // 如果 nums1 的长度大于 nums2,交换两个数组,确保 nums1 是较短的数组
       if (m > n) {
           return findMedianSortedArrays(nums2, nums1);
       }

       // 初始化二分查找的左右边界
       int imin = 0, imax = m;

       // 二分查找循环
       while (imin <= imax) {
           // 计算中间位置的索引
           int i = (imin + imax) / 2;
           // 计算合并后数组的中位数位置的索引
           int j = (m + n + 1) / 2 - i;

           // 如果 nums1 的中间值小于 nums2 的对应位置的值,说明 i 太小,需要增加 i
           if (i < imax && nums2[j - 1] > nums1[i]) {
               imin = i + 1;
           // 如果 nums1 的中间值大于 nums2 的对应位置的值,说明 i 太大,需要减小 i
           } else if (i > imin && nums1[i - 1] > nums2[j]) {
               imax = i - 1;
           // 如果找到了正确的划分,计算中位数
           } else {
               // 初始化左边的最大值
               int maxLeft = 0;
               // 如果 i 为 0,maxLeft 为 nums2 的对应位置的值
               if (i == 0) {
                   maxLeft = nums2[j - 1];
               // 如果 j 为 0,maxLeft 为 nums1 的对应位置的值
               } else if (j == 0) {
                   maxLeft = nums1[i - 1];
               // 否则,取两个数组对应位置值的最大值
               } else {
                   maxLeft = Math.max(nums1[i - 1], nums2[j - 1]);
               }

               // 如果合并后的数组长度为奇数,返回 maxLeft 作为中位数
               if ((m + n) % 2 == 1) {
                   return maxLeft;
               }

               // 初始化右边的最小值
               int minRight = 0;
               // 如果 i 为数组长度,minRight 为 nums2 的对应位置的值
               if (i == m) {
                   minRight = nums2[j];
               // 如果 j 为数组长度,minRight 为 nums1 的对应位置的值
               } else if (j == n) {
                   minRight = nums1[i];
               // 否则,取两个数组对应位置值的最小值
               } else {
                   minRight = Math.min(nums1[i], nums2[j]);
               }

               // 如果合并后的数组长度为偶数,返回 maxLeft 和 minRight 的平均值作为中位数
               return (maxLeft + minRight) / 2.0;
           }
       }

       // 如果循环结束仍未找到中位数,抛出异常
       throw new IllegalArgumentException("Input arrays are not sorted or of zero length");
   }
}

(三)时间复杂度和空间复杂度

1. 时间复杂度
  • 时间复杂度是 O(log(min(m, n)))。
  • 这是因为代码使用了二分查找算法来确定合并后数组的中位数位置。
  • 在最坏的情况下,二分查找需要进行 log(min(m, n)) 次比较,其中 m 和 n 分别是两个数组的长度。
  • 由于我们总是比较较短数组的元素,所以时间复杂度不会超过较短数组的长度。
2. 空间复杂度
  • 空间复杂度是 O(1)。
  • 代码中没有使用任何额外的数据结构来存储数据,所有的计算都是在常数空间内完成的。
  • 变量 i, j, imin, imax, maxLeft, 和 minRight 都是在常数空间内进行操作。

(四)总结知识点

  1. 递归:通过递归调用来处理数组长度的交换,确保较短的数组作为 nums1

  2. 二分查找:使用二分查找法来确定合并后数组的中位数位置,这是一种高效的搜索算法,适用于有序数组。

  3. 边界条件处理:在计算 maxLeftminRight 时,需要处理数组的边界情况,例如数组的开头和结尾。

  4. 数学逻辑:理解中位数的定义,即在有序数组中,中位数是将数组分为两个部分的元素,且这两部分的元素数量相等(或相差1)。

  5. 条件判断:根据数组的总长度是奇数还是偶数,决定返回单个中位数还是两个中位数的平均值。

  6. 数据类型处理:在计算平均值时,确保结果为浮点数,以处理可能出现的小数情况。

  7. 异常处理:在循环结束后,如果未找到中位数,抛出异常,提示输入数组可能未排序或长度为零。

  8. 算法优化:通过二分查找优化算法的时间复杂度,使其达到 O(log(min(m, n))),这是解决该问题所需的最低时间复杂度。

  9. 代码结构:清晰的代码结构和逻辑,使得算法易于理解和维护。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。

  • 29
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直学习永不止步

谢谢您的鼓励,我会再接再厉的!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值