LeetCode 刷题记录 4. Median of Two Sorted Arrays

本文介绍了如何解决LeetCode中寻找两个有序数组的中位数问题,提出了时间复杂度为O(log (m+n))的解决方案。通过二分法找到第K小的元素,针对不同情况讨论了如何确定数组的起始位置和K值的变化,以找到正确答案。文中还提到了代码实现中的一些注意事项,如Java中整数和最大值的处理。
摘要由CSDN通过智能技术生成

题目:
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)).

You may assume nums1 and nums2 cannot be both empty.

Example 1:

nums1 = [1, 3]
nums2 = [2]

The median is 2.0
Example 2:

nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5
方法1:
类似于归并排序,将两个有序列表合并为一个大的有序列表,然后根据列表的元素是奇数还是偶数分别求中间值,但这种方法复杂度O(m+n) 不符合题目要求
C++:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        int ans[n + m];
        double median;
        int i = 0, j = 0, index = 0;
        while(i < m && j < n){
            if(nums1[i] <= nums2[j]){
                ans[index++] = nums1[i++];
            } else {
                ans[index++] = nums2[j++];
            }
        }
        while(i < m) ans[index++] = nums1[i++];
        while(j < n) ans[index++] = nums2[j++];
        if(index % 2){
            median = (double)ans[index / 2];
        } else {
            median = (ans[index / 2] + ans[index / 2 - 1]) / 2.0;
        }
        return median;
    }
};

java:
java中整数不能直接当布尔值来用 所以不能用if(index % 2) 而是用if(index % 2 == 1)

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int[] ans = new int[m + n];
        int i = 0, j = 0, index = 0;
        double median;
        while(i < m && j < n){
            if(nums1[i] <= nums2[j]){
                ans[index++] = nums1[i++];
            } else {
                ans[index++] = nums2[j++];
            }
        }
        while(i < m) ans[index++] = nums1[i++];
        while(j < n) ans[index++] = nums2[j++];
        if(index % 2 == 1){
            median = (double)ans[index / 2];
        } else {
            median = (ans[index / 2] + ans[index / 2 - 1]) / 2.0;
        }
        return median;
    }
}

Python:

class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        m = len(nums1)
        n = len(nums2)
        ans = [0] * (n + m)
    
        i, j , index = 0, 0, 0
        while i < m and j < n:
            if nums1[i] <= nums2[j]:
                ans[index] = nums1[i]
                i = i + 1
                index = index + 1
            else:
                ans[index] = nums2[j]
                j = j + 1
                index = index + 1
            
        
        while i < m: 
            ans[index] = nums1[i]
            i = i + 1
            index = index + 1
        while j < n:
            ans[index] = nums2[j]
            j = j + 1
            index = index + 1
       # print ans
        if index % 2:
            median = ans[index / 2]
        else:
            median = (ans[index / 2] + ans[index / 2 - 1]) / 2.0
        
        return median;

方法2:
时间复杂度为O(log (m+n)),所以要用到二分法
核心方法是findKth函数,找到第两个数组第k小的数
首先我们要判断两个数组长度之和是否为奇数(用&0x1速度比较快)分两种情况

好,这里我们需要定义一个函数来在两个有序数组中找到第K个元素,下面重点来看如何实现找到第K个元素。首先,为了避免产生新的数组从而增加时间复杂度,我们使用两个变量i和j分别来标记数组nums1和nums2的起始位置。然后来处理一些corner cases,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果K=1的话,那么我们只要比较nums1和nums2的起始位置i和j上的数字就可以了。难点就在于一般的情况怎么处理?因为我们需要在两个有序数组中找到第K个元素,为了加快搜索的速度,我们要使用二分法,那么对谁二分呢,数组么?其实要对K二分,意思是我们需要分别在nums1和nums2中查找第K/2个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第K/2个数字,所以我们需要先check一下,数组中到底存不存在第K/2个数字,如果存在就取出来,否则就赋值上一个整型最大值。如果某个数组没有第K/2个数字,那么我们就淘汰另一个数组的前K/2个数字即可。举个例子来说吧,比如 nums1 = {3},nums2 = {2, 4, 5, 6, 7},K=4,我们要找两个数组混合中第4个数字,那么我们分别在 nums1 和 nums2 中找第2个数字,我们发现 nums1 中只有一个数字,不存在第二个数字,那么 nums2 中的前2个数字可以直接跳过,为啥呢,因为我们要求整个混合数组的第4个数字,不管 nums1 中的那个数字是大是小,第4个数字绝不会出现在 nums2 的前两个数字中,所以可以直接跳过。

有没有可能两个数组都不存在第K/2个数字呢,这道题里是不可能的,因为我们的K不是任意给的,而是给的m+n的中间值,所以必定至少会有一个数组是存在第K/2个数字的。最后就是二分法的核心啦,比较这两个数组的第K/2小的数字midVal1和midVal2的大小,如果第一个数组的第K/2个数字小的话,那么说明我们要找的数字肯定不在nums1中的前K/2个数字,所以我们可以将其淘汰,将nums1的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归。反之,我们淘汰nums2中的前K/2个数字,并将nums2的起始位置向后移动K/2个,并且此时的K也自减去K/2,调用递归即可(by [LeetCode] Median of Two Sorted Arrays 两个有序数组的中位数

注意点: 不能判断midVal1和midVal2相等时,返回midVal1,在某些样例会出错

 if(midA < midB){
            return findKth(A, i + k / 2, B, j, k - k / 2);
 } else if(midA > midB) {
            return findKth(A, i, B, j + k / 2, k - k / 2);
 } else {
 			return midA;
 }

如:
A:[1,2]
B:[1,2]
查找两个数组的第2小的数时,k=2,k/2 = 1,第一次二分都指向元素的首元素,它们都相等,就返回1,此时正确
但查找两个数组的第3小的数时,k=3,k/2 = 1,第一次二分都指向元素的首元素,它们都相等,就返回1,此时不正确,应该返回2
改进方法:
三分支改为两分支

  if(midA < midB){
            return findKth(A, i + k / 2, B, j, k - k / 2);
   } else {
            return findKth(A, i, B, j + k / 2, k - k / 2);
   }

A:[1,2]
B:[1,2]
查找两个数组的第2小的数时,k=2,k/2 = 1,第一次二分都指向元素的首元素,它们相等,不满足midA < midB,将B数组前一半舍弃,j = 1,k = 1,到达递归边界,取A[0]和B[1]中小者,即为1,正确
查找两个数组的第3小的数时,k=3,k/2 = 1,第一次二分都指向元素的首元素,它们相等,不满足midA < midB,将B数组前一半舍弃,j = 1,k = 2,k/2 = 1,第二次二分,A[0] < B[1],将A数组前一半舍弃,i= 1,k=1,取A[1]和B[1]中小者,即为2,正确
C++:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        int total = m + n;
        //如果总长度是奇数
        if(total & 0x1){
            return (double)findKth(nums1, 0, nums2, 0, total / 2 + 1);
        } else {
           // cout << findKth(nums1, 0, nums2, 0, total / 2) << endl;
            //cout << findKth(nums1, 0, nums2, 0, total / 2 + 1) << endl;
            
            return (findKth(nums1, 0, nums2, 0, total / 2) + findKth(nums1, 0, nums2, 0, total / 2 + 1)) / 2.0;
        }
    }
    //寻找A和B数组中第k小的数
    //i,j分别为指针 指向开始查找的位置
    int findKth(vector<int>& A, int i, vector<int>& B, int j, int k){
        //i超过A数组的边界 说明A中没有要查找的元素 直接返回B中的元素
        if(i >= A.size()) return B[j + k - 1];
        //j超过B数组的边界 说明B中没有要查找的元素 直接返回A中的元素
        if(j >= B.size()) return A[i + k - 1];
        if(k == 1) return min(A[i], B[j]);
        //两个数组同时二分
        //每个数组找到第k/2个元素 如果越界 取最大值INT_MAX
        int midA = ((i + k / 2 - 1) < A.size()) ? A[i + k / 2 - 1] : INT_MAX;
        int midB = ((j + k / 2 - 1) < B.size()) ? B[j + k / 2 - 1] : INT_MAX;
        if(midA < midB){
            return findKth(A, i + k / 2, B, j, k - k / 2);
        } else {
            return findKth(A, i, B, j + k / 2, k - k / 2);
        }
        
    }
       
};

java:
注意点:Java中整数最大值为Integer.MAX_VALUE

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int total = m + n;
        //如果总长度是奇数
        if(total % 2 == 1){
            return (double)findKth(nums1, 0, nums2, 0, total / 2 + 1);
        } else {
           
            
            return (findKth(nums1, 0, nums2, 0, total / 2) + findKth(nums1, 0, nums2, 0, total / 2 + 1)) / 2.0;
        }
    }
    //寻找A和B数组中第k小的数
    //i,j分别为指针 指向开始查找的位置
    int findKth(int[] A, int i, int[] B, int j, int k){
        //i超过A数组的边界 说明A中没有要查找的元素 直接返回B中的元素
        if(i >= A.length) return B[j + k - 1];
        //j超过B数组的边界 说明B中没有要查找的元素 直接返回A中的元素
        if(j >= B.length) return A[i + k - 1];
        if(k == 1) return Math.min(A[i], B[j]);
        //两个数组同时二分
        //每个数组找到第k/2个元素 如果越界 取最大值INT_MAX
        int midA = ((i + k / 2 - 1) < A.length) ? A[i + k / 2 - 1] : Integer.MAX_VALUE;
        int midB = ((j + k / 2 - 1) < B.length) ? B[j + k / 2 - 1] : Integer.MAX_VALUE;
        if(midA < midB){
            return findKth(A, i + k / 2, B, j, k - k / 2);
        } else {
            return findKth(A, i, B, j + k / 2, k - k / 2);
        }
        
    }
}

Python:
注意点:def findKth(A, i, B, j, k)定义在方法里
整数最大值:sys.maxsize

class Solution(object):
    
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        
        def findKth(A, i, B, j, k):
        
            if i >= len(A): return B[j + k - 1]

            if j >= len(B): return A[i + k - 1]
            if k == 1: return min(A[i], B[j])

            midA = A[i + k / 2 - 1] if (i + k / 2 - 1) < len(A) else sys.maxsize
            midB = B[j + k / 2 - 1] if (j + k / 2 - 1) < len(B) else sys.maxsize

            if midA < midB:
                return findKth(A, i + k / 2, B, j, k - k / 2)
            else:
                return findKth(A, i, B, j + k / 2, k - k / 2)
        m = len(nums1)
        n = len(nums2)
        
        total = m + n
        
        if total % 2 == 1:
            return findKth(nums1, 0, nums2, 0, total // 2 + 1)
        else:
            return (findKth(nums1, 0, nums2, 0, total // 2) + findKth(nums1, 0, nums2, 0, total // 2 + 1)) / 2.0
            

方法3:
迭代法:
核心是理解中位数的概念是左右部分相等,且左部分的值小于右部分的值
用值cut1和cut2将两数组分为左右部分,使得左右部分相等或者右部分的数比左部分多一位
定义l1,l2为左部分的最大值,为nums1[cut1 - 1],nums2[cut1 - 1]
r1,r2为右部分的最小值,为nums1[cut1],nums2[cut1]
为实现两部分相等需满足cut2 = (m + n) / 2 - cut1

为了确保cut2不为负数,即要求二分的数组是长度较小的数组,现在较小的数组中二分确定cut1,然后确定cut2

cpp:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        if(m > n) return findMedianSortedArrays(nums2, nums1);
        int lo = 0;
        int hi = m;
        while(lo <= hi){
            int cut1 = (lo + hi) / 2;
            int cut2 = (m + n) / 2 - cut1;
            int l1 = (cut1 == 0) ? INT_MIN : (nums1[cut1 - 1]);
            int l2 = (cut2 == 0) ? INT_MIN : (nums2[cut2 - 1]);
            int r1 = (cut1 == m) ? INT_MAX : (nums1[cut1]);
            int r2 = (cut2 == n) ? INT_MAX : (nums2[cut2]);
            if(l1 > r2){
                hi = cut1 - 1;
            } else if(l2 > r1) {
                lo = cut1 + 1;
            } else {
                return (m + n) % 2 ? min(r1, r2) : (max(l1, l2) + min(r1, r2)) / 2.0;
            }
        }
        return 0.0;
    }
};

java:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        if(m > n) return findMedianSortedArrays(nums2, nums1);
        int lo = 0;
        int hi = m;
        while(lo <= hi){
            int cut1 = (lo + hi) / 2;
            int cut2 = (m + n) / 2 - cut1;
            int l1 = (cut1 == 0) ? Integer.MIN_VALUE : (nums1[cut1 - 1]);
            int l2 = (cut2 == 0) ? Integer.MIN_VALUE : (nums2[cut2 - 1]);
            int r1 = (cut1 == m) ? Integer.MAX_VALUE : (nums1[cut1]);
            int r2 = (cut2 == n) ? Integer.MAX_VALUE : (nums2[cut2]);
            if(l1 > r2){
                hi = cut1 - 1;
            } else if(l2 > r1) {
                lo = cut1 + 1;
            } else {
                return (m + n) % 2 == 1? Math.min(r1, r2) : (Math.max(l1, l2) + Math.min(r1, r2)) / 2.0;
            }
        }
        return 0.0;
        
    }
}

python:
不能用 findMedianSortedArrays(nums2, nums1)

class Solution(object):
    
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        
        m , n = len(nums1), len(nums2)
        if m > n: 
            nums1, m, nums2, n = nums2, n, nums1, m
        lo = 0
        hi = m
        while lo <= hi:
            cut1 = (lo + hi) >> 1
           # print cut1
            cut2 = ((m + n) >> 1) - cut1
           # print cut2
            l1 = -sys.maxsize - 1 if (cut1 == 0) else (nums1[cut1 - 1])
            l2 = -sys.maxsize - 1 if (cut2 == 0) else (nums2[cut2 - 1])
            r1 = sys.maxsize if (cut1 == m) else (nums1[cut1])
            r2 = sys.maxsize if (cut2 == n) else (nums2[cut2])
            if l1 > r2:
                hi = cut1 - 1
            elif l2 > r1:
                lo = cut1 + 1
            else:
                if (m + n) % 2:
                    return min(r1, r2)
                else:
                    return (max(l1, l2) + min(r1, r2)) / 2.0
                
            
        
        return 0.0;
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值