算法--寻找两个正序数组的中位数

寻找两有序数组中位数

描述

给定两个大小为 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

分析及代码

参考:
https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int leftLength = nums1.length;
        int rightLength = nums2.length;
        // 为了保证第一个数组比第二个数组小(或者相等)
        if (leftLength > rightLength) {
            return findMedianSortedArrays(nums2, nums1);
        }
        // 分割线左边的所有元素需要满足的个数 m + (n - m + 1) / 2;
        // 两个数组长度之和为偶数时,当在长度之和上+1时,由于整除是向下取整,所以不会改变结果
        // 两个数组长度之和为奇数时,按照分割线的左边比右边多一个元素的要求,此时在长度之和上+1,就会被2整除,会在原来的数
        //的基础上+1,于是多出来的那个1就是左边比右边多出来的一个元素
        int totalLeft = (leftLength + rightLength + 1) / 2;
        // 在 nums1 的区间 [0, leftLength] 里查找恰当的分割线,
        // 使得 nums1[i - 1] <= nums2[j] && nums2[j - 1] <= nums1[i]
        int left = 0;
        int right = leftLength;
        // nums1[i - 1] <= nums2[j]
        //  此处要求第一个数组中分割线的左边的值 不大于(小于等于) 第二个数组中分割线的右边的值
        // nums2[j - 1] <= nums1[i]
        //  此处要求第二个数组中分割线的左边的值 不大于(小于等于) 第一个数组中分割线的右边的值
        // 循环条件结束的条件为指针重合,即分割线已找到
        while (left < right) {
            // 二分查找,此处为取第一个数组中左右指针下标的中位数,决定起始位置
            // 此处+1首先是为了不出现死循环,即left永远小于right的情况
            // left和right最小差距是1,此时下面的计算结果如果不加1会出现i一直=left的情况,而+1之后i才会=right
            // 于是在left=i的时候可以破坏循环条件,其次下标+1还会保证下标不会越界,因为+1之后向上取整,保证了
            // i不会取到0值,即i-1不会小于0
            // 此时i也代表着在一个数组中左边的元素的个数
            int i = left + (right - left + 1) / 2;
            // 第一个数组中左边的元素个数确定后,用左边元素的总和-第一个数组中元素的总和=第二个元素中左边的元素的总和
            // 此时j就是第二个元素中左边的元素的个数
            int j = totalLeft - i;
            // 此处用了nums1[i - 1] <= nums2[j]的取反,当第一个数组中分割线的左边的值大于第二个数组中分割线的右边的值
            // 说明又指针应该左移,即-1
            if (nums1[i - 1] > nums2[j]) {
                // 下一轮搜索的区间 [left, i - 1]
                right = i - 1;
                // 此时说明条件满足,应当将左指针右移到i的位置,至于为什么是右移,请看i的定义
            } else {
                // 下一轮搜索的区间 [i, right]
                left = i;
            }
        }
        // 退出循环时left一定等于right,所以此时等于left和right都可以
        // 为什么left一定不会大于right?因为left=i。
        // 此时i代表分割线在第一个数组中所在的位置
        // nums1[i]为第一个数组中分割线右边的第一个值
        // nums[i-1]即第一个数组中分割线左边的第一个值
        int i = left;
        // 此时j代表分割线在第二个数组中的位置
        // nums2[j]为第一个数组中分割线右边的第一个值
        // nums2[j-1]即第一个数组中分割线左边的第一个值
        int j = totalLeft - i;
        // 当i=0时,说明第一个数组分割线左边没有值,为了不影响
        // nums1[i - 1] <= nums2[j] 和 Math.max(nums1LeftMax, nums2LeftMax)
        // 的判断,所以将它设置为int的最小值
        int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
        // 等i=第一个数组的长度时,说明第一个数组分割线右边没有值,为了不影响
        // nums2[j - 1] <= nums1[i] 和 Math.min(nums1RightMin, nums2RightMin)
        // 的判断,所以将它设置为int的最大值
        int nums1RightMin = i == leftLength ? Integer.MAX_VALUE : nums1[i];
        // 当j=0时,说明第二个数组分割线左边没有值,为了不影响
        // nums2[j - 1] <= nums1[i] 和 Math.max(nums1LeftMax, nums2LeftMax)
        // 的判断,所以将它设置为int的最小值
        int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
        // 等j=第二个数组的长度时,说明第二个数组分割线右边没有值,为了不影响
        // nums1[i - 1] <= nums2[j] 和 Math.min(nums1RightMin, nums2RightMin)
        // 的判断,所以将它设置为int的最大值
        int nums2RightMin = j == rightLength ? Integer.MAX_VALUE : nums2[j];
        // 如果两个数组的长度之和为奇数,直接返回两个数组在分割线左边的最大值即可
        if (((leftLength + rightLength) % 2) == 1) {
            return Math.max(nums1LeftMax, nums2LeftMax);
        } else {
            // 如果两个数组的长度之和为偶数,返回的是两个数组在左边的最大值和两个数组在右边的最小值的和的二分之一
            // 此处不能被向下取整,所以要强制转换为double类型
            return (double) ((Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin))) / 2;
        }
    }
}
### 问题分析 给定两个长度为 $N$ 的升序整型数 $A$ 和 $B$,目标是找出这两个合并后形成的升序数中位数。由于数长度相同且已排序,可以利用二分查找的思想设计一个时间复杂度为 $O(\log N)$ 的高效算法。 合并后的数长度为 $2N$,因此中位数是合并数中第 $N$ 个元素(下中位数)。 ### 算法思路 该问题可以通过对两个进行二分查找来实现: - 每次在两个中各取一个中间位置的元素进行比较。 - 如果 $A[mid] &lt; B[mid]$,说明中位数不可能在 $A$ 的前半部分,也不在 $B$ 的后半部分。 - 反之,如果 $A[mid] &gt; B[mid]$,说明中位数不可能在 $B$ 的前半部分,也不在 $A$ 的后半部分。 - 每次递归缩小查找范围,直到找到中位数。 ### C语言实现 ```c #include &lt;stdio.h&gt; int findMedian(int A[], int B[], int leftA, int rightA, int leftB, int rightB) { // 当只剩下一个元素时,取较小的那个 if (leftA == rightA) { return (A[leftA] &lt; B[leftB]) ? A[leftA] : B[leftB]; } int midA = (leftA + rightA) / 2; int midB = (leftB + rightB) / 2; // 比较两个中位数 if (A[midA] == B[midB]) { return A[midA]; // 相等时即为中位数 } else if (A[midA] &lt; B[midB]) { // A的中位数小于B的中位数,舍弃A的前半部分和B的后半部分 return findMedian(A, B, midA + 1, rightA, leftB, midB); } else { // A的中位数大于B的中位数,舍弃B的前半部分和A的后半部分 return findMedian(A, B, leftA, midA, midB + 1, rightB); } } int main() { int A[] = {11, 13, 15}; int B[] = {6, 8, 20}; int N = sizeof(A) / sizeof(A[0]); int median = findMedian(A, B, 0, N - 1, 0, N - 1); printf(&quot;两个序数中位数为: %d\n&quot;, median); return 0; } ``` ### 示例说明 - 输入数 `A = {11, 13, 15}` 和 `B = {6, 8, 20}`。 - 合并后的数为 `{6, 8, 11, 13, 15, 20}`。 - 中位数是第3个元素,即 `11`。 ### 时间复杂度分析 该算法采用分治策略,每次将问题规模减半,因此时间复杂度为 $O(\log N)$,空间复杂度为 $O(1)$(不考虑递归栈空间)。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值