4. 寻找两个正序数组的中位数

一、问题

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

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

二、解题思路

  • 中位数的定义

    • 如果两个数组的总长度为奇数,那么中位数是合并后数组的第 (m+n)/2 个元素。
    • 如果总长度为偶数,中位数是合并后数组的第 (m+n)/2(m+n)/2 + 1 个元素的平均值。
  • 二分查找的优化

    • 我们可以不必真的将两个数组合并,而是通过二分查找的方法直接找到合并后的中位数。
    • 通过在较小的数组中进行二分查找,可以以 O(log(min(m, n))) 的时间复杂度找到中位数。
  • 关键思想

    • 我们将问题转化为寻找合并后数组中的第 k 小元素,通过二分查找不断缩小 nums1nums2 的搜索范围。
    • 每次比较 nums1nums2 中对应位置的元素,丢弃较小的一半元素,从而缩小问题规模。

三、代码

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

        // 确保 nums1 是较短的数组,这样可以减少二分查找的范围
        if (m > n) {
            // 如果 nums1 比 nums2 长,交换它们,以确保 nums1 是较短的数组
            return findMedianSortedArrays(nums2, nums1);
        }

        // 初始化二分查找的范围,low 和 high 是 nums1 中的二分查找范围
        int low = 0, high = m;

        // totalLeft 表示合并后的数组在分割时左侧的元素个数(加 1 是为了处理总长度为奇数的情况)
        int totalLeft = (m + n + 1) / 2;

        // 开始二分查找
        while (low <= high) {
            // i 是 nums1 的分割点
            int i = (low + high) / 2;
            // j 是 nums2 的分割点,保证左侧有 totalLeft 个元素
            int j = totalLeft - i;

            // 边界条件:如果 i == 0,表示 nums1 左侧没有元素,设为负无穷大
            // 否则,取 nums1[i - 1] 作为 nums1 左侧的最大值
            int nums1LeftMax = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1];

            // 边界条件:如果 i == m,表示 nums1 右侧没有元素,设为正无穷大
            // 否则,取 nums1[i] 作为 nums1 右侧的最小值
            int nums1RightMin = (i == m) ? Integer.MAX_VALUE : nums1[i];

            // 边界条件:如果 j == 0,表示 nums2 左侧没有元素,设为负无穷大
            // 否则,取 nums2[j - 1] 作为 nums2 左侧的最大值
            int nums2LeftMax = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1];

            // 边界条件:如果 j == n,表示 nums2 右侧没有元素,设为正无穷大
            // 否则,取 nums2[j] 作为 nums2 右侧的最小值
            int nums2RightMin = (j == n) ? Integer.MAX_VALUE : nums2[j];

            // 检查当前的分割是否正确:即左侧所有元素小于等于右侧所有元素
            if (nums1LeftMax <= nums2RightMin && nums2LeftMax <= nums1RightMin) {
                // 如果两个数组的总长度是奇数,中位数是左侧部分的最大值
                if ((m + n) % 2 == 1) {
                    return Math.max(nums1LeftMax, nums2LeftMax);
                } 
                // 如果总长度是偶数,中位数是左侧最大值和右侧最小值的平均值
                else {
                    return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2.0;
                }
            } 
            // 如果 nums1LeftMax > nums2RightMin,说明 i 位置太大,应该向左移动
            else if (nums1LeftMax > nums2RightMin) {
                high = i - 1; // 更新二分查找范围,减少 high 值
            } 
            // 如果 nums2LeftMax > nums1RightMin,说明 i 位置太小,应该向右移动
            else {
                low = i + 1; // 更新二分查找范围,增加 low 值
            }
        }

        // 理论上不会到达这里,因为总能找到中位数
        throw new IllegalArgumentException("Input arrays are not sorted.");
    }
}

四、时间和空间复杂度

  • 时间复杂度
    • 由于在较短的数组上进行二分查找,时间复杂度是 O(log(min(m, n)))。这是因为每次都在较短数组的范围内缩小一半的搜索区间。
  • 空间复杂度
    • 空间复杂度为 O(1),因为我们只使用了常量的额外空间来存储变量。
寻找两个正序数组中位数可以使用归并的方式,合并两个有序数组,得到一个大的有序数组。然后找到大的有序数组的中间位置的元素,即为中位数。另一种方法是使用双指针的方式,维护两个指针,初始时分别指向两个数组的下标0的位置。每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。这样可以在O(log(m+n))的时间复杂度内找到中位数。 具体步骤如下: 1. 初始化指针p1和p2分别指向两个数组的起始位置0。 2. 判断两个指针所指的元素大小,较小的元素所在的指针后移一位,直到其中一个指针到达数组末尾。 3. 若两个数组的长度之和为奇数,那么中位数即为当前指针指向的元素; 若两个数组的长度之和为偶数,那么中位数为当前指针指向的元素与其下一个元素的平均值。 4. 返回中位数作为结果。 需要注意的是,为了保证时间复杂度为O(log(m+n)),在每次移动指针时,应该移动的步数应该是当前指针所在数组长度的一半,即k/2,其中k为两个数组的长度之和。 以下是一个示例代码,用于说明上述方法的实现: ``` int findMedianSortedArrays(int[] nums1, int[] nums2) { int m = nums1.length; int n = nums2.length; int total = m + n; int middle = total / 2; int p1 = 0, p2 = 0; int prev = 0, curr = 0; for (int i = 0; i <= middle; i++) { prev = curr; if (p1 < m && (p2 >= n || nums1[p1 < nums2[p2])) { curr = nums1[p1++]; } else { curr = nums2[p2++]; } } if (total % 2 == 0) { return (prev + curr) / 2; } else { return curr; } } ``` 该方法可以在O(log(m+n))的时间复杂度内找到两个正序数组中位数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [算法寻找两个正序数组中位数。](https://blog.csdn.net/en_joker/article/details/107179641)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [寻找两个正序数组中位数](https://blog.csdn.net/wulila/article/details/124483500)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值