高频面试题-百度/字节/滴滴- 二分查找 | 数组划分:力扣4. 寻找两个正序数组的中位数 (C++调试方法)

1、题目描述:

困难题
在这里插入图片描述

2、题解:

方法1:暴力法
思路:

1、使用归并的方式,合并两个有序数组,得到一个大的有序数组。大的有序数组的中间位置的元素,即为中位数。

2、不需要合并两个有序数组,只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是
已知的。维护两个指针,初始时分别指向两个数组的下标 0 的位置,每次将指向较小值的指针后移一位(如果一个指针已经
到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。

合并两个有序的数组后找中位数

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        #暴力法,合并两个有序的数组
        res = []
        m,n = len(nums1),len(nums2)
        if m == 0:
            if n % 2 == 0:
                return (nums2[n//2] + nums2[n // 2 - 1]) / 2
            else :
                return nums2[n // 2]
        if n == 0:
            if m % 2 == 0:
                return (nums1[m // 2] + nums1[m // 2 - 1]) / 2
            else:
                return nums1[m // 2]
        i ,j = 0,0
        while i < m and j < n:
            if nums1[i] < nums2[j]:
                res.append(nums1[i])
                i += 1
            else:
                res.append(nums2[j])
                j += 1
        res += nums1[i:] if i < m else nums2[j:]
        size = len(res)
        if size % 2 == 0:
            return (res[size // 2] + res[size // 2 - 1]) / 2
        else:
            return res[size // 2]

方法2:一次扫描
方法1的空间复杂为O(M+N),我们可以优化空间复杂度,思路:

先不考虑长度是奇数还是偶数,先创建一个方法,在两个有序数组中,求第k个小的数。
最后再考虑奇数、偶数的问题

python代码如下:

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        # 遍历,找第k小的数
        def getKthnum(nums1, nums2, k):
            if not nums1 and not nums2: return -1
            if not nums1: return nums2[k - 1]
            if not nums2: return nums1[k - 1]
            index1, index2 = 0, 0
            while True:
                # 特殊情况
                if index1 == m:
                    return nums2[index2 + k - 1]
                if index2 == n:
                    return nums1[index1 + k - 1]
                if k == 1:
                    return min(nums1[index1], nums2[index2])

                # 一般情况
                if index1 < m and index2 < n and nums1[index1] <= nums2[index2]:
                    index1 += 1
                    k -= 1
                elif index1 < m and index2 < n and nums1[index1] > nums2[index2]:
                    index2 += 1
                    k -= 1

        m, n = len(nums1), len(nums2)
        totalLen = m + n
        if totalLen % 2 == 1:
            return getKthnum(nums1, nums2, totalLen // 2 + 1)
        else:
            return (getKthnum(nums1, nums2, totalLen // 2) + getKthnum(nums1, nums2, totalLen // 2 + 1)) / 2

或者,用另外一种写法,
思路:

我们找totlaLen // 2 + 1个数,然后每次找的时候都记录上次得到的数(left = right)
最后处理奇数和偶数的问题。
class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m,n = len(nums1),len(nums2)
        totalLen = m + n
        left,right = -1,-1
        index1,index2 = 0,0
        for _ in range(totalLen//2 + 1):
            left = right
            if index1 < m and (index2 >= n or nums1[index1] <= nums2[index2]):
                right = nums1[index1]
                index1 += 1
            else:
                right = nums2[index2]
                index2 += 1
        if totalLen % 2 == 1:
            return right
        else:
            return (left + right) / 2

方法3:二分查找
思路:

中位数的定义:当m + n为奇数时,中位数是两个有序数组的第(m+n)/2个元素,当m+n为偶数时,中位数是(m+n)/2(m+n)/2+1
个元素的平均值。所以可以转化成寻找两个有序数组中的第k小的数,k为(m+n)/2(m+n)/2+1。

分别求出nums1,nums2的第 k / 2个数 a 和 b.
然后比较 a 和 b:
	当a < b ,说明第 k 个数位于 a数组的第 k / 2个数后半段,或者b数组的 第 k / 2 个数前半段,
	问题规模缩小了一半,然后递归处理就行。
- 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
- 这里的 "/" 表示整除
- nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1- nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1- 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2- 这样 pivot 本身最大也只能是第 k-1 小的元素
- 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
- 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
- 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数

Python代码如下:

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        #二分查找
        def getKthElement(k):
            index1,index2 = 0,0
            while True:
                #特殊情况
                if index1 == m:
                    return nums2[index2 + k -1]
                if index2 == n:
                    return nums1[index1 + k - 1]
                if k == 1:
                    return min(nums1[index1],nums2[index2])

                #一般情况
                newIndex1 = min(index1 + k // 2 - 1,m - 1)
                newIndex2 = min(index2 + k // 2 - 1 , n - 1)
                pivot1,pivot2 = nums1[newIndex1],nums2[newIndex2]
                if pivot1 <= pivot2:
                    k -= newIndex1 - index1 + 1
                    index1 = newIndex1 + 1
                else:
                    k -= newIndex2 - index2 + 1
                    index2 = newIndex2 + 1
        m,n = len(nums1),len(nums2)
        totalLen = m + n
        if totalLen % 2 == 1:
            return getKthElement((totalLen + 1) // 2)
        else:
            return ((getKthElement(totalLen // 2)) + getKthElement(totalLen // 2 + 1)) / 2

C++实现:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        //二分查找
        int col = nums1.size(),row = nums2.size();
        int total = col + row;
        if (total % 2 == 1){
            return getKthElement(nums1,nums2,total / 2 + 1);
        }
        else{
            return (getKthElement(nums1,nums2,total / 2) + getKthElement(nums1,nums2,total / 2 + 1)) / 2.0;
        }
    }
    int getKthElement(vector<int>& nums1,vector<int>& nums2,int k){
        int col = nums1.size(),row = nums2.size();
        int index1 = 0,index2 = 0;
        while (true){
            if (index1 == col){
                return nums2[index2 + k - 1];
            }
            if (index2 == row){
                return nums1[index1 + k - 1];
            }
            if (k == 1){
                return min(nums1[index1],nums2[index2]);
            }
            int newindex1 = min(index1 + k / 2 - 1,col - 1);
            int newindex2 = min(index2 + k / 2 - 1,row - 1);
            if (nums1[newindex1] <= nums2[newindex2]){
                k -= newindex1 - index1 + 1;
                index1 = newindex1 + 1;
            }
            else{
                k -= newindex2 - index2 + 1;
                index2 = newindex2 + 1;
            }
        }
    }
};

方法4:数组划分

分割: 割会分有序数组为两部分,割左边必定小于右边,割的左右两边会有两个元素 lmax,rmin。当数组长度为
偶数时,割在两数中间;当数组长度为奇数时,割在1个数上。
可以把两个数组合成一个数组,分奇数和偶数进行分别计算。当然也可以考虑加'#'号,当然这里是虚拟加的'#'
    [1,2,3] -> [#,1,#,2,#,3,#]
    [5,6] -> [#,5,#,6,#]
这样处理,两个数组的长度和必定是偶数,每个位置可以通过/2得到原来元素的位置,分割点这样对于numi来说,必定有以下两个部分之一成立:
    lmaxi = (ci - 1) // 2位置上的元素
    rmaxi = ci // 2 位置上的元素
二分的依据:
    m,n = len(nums1),len(nums2)
    left,right = 0,len(nums1) * 2
    当lmax1 > rmin2时:
        说明数组1的左边元素太多,减小c1,那么c2 = m+n-c1就相应增大;c1向左二分,就让 right = c1-1
    如果 lmax2 > rmin1时:
        说明数组2左边的元素太多,减小c2,那么c1= m+n - c2,就相应增大;c1向右二分,就让left = c1+1
    最终返回 (max(lmax1,rmax1) + min(rmin1,rmin2)) / 2
当然也要处理以下4中情况:
    lmax1 = float('-inf') if c1 == 0 else nums1[(c1 - 1) // 2],割在数组1开头,割的左边是空,可以认为lmax1 = float('-inf')
    rmin1 = float('inf') if c1 == 2 * m else nums1[c1 // 2],割在数组1的结尾,割的右边是空,可以认为rmin1 = float('inf')
    lmax2 = float('-inf') if c2 == 0 else nums2[(c2 - 1) // 2],割在数组2的开头,割的左边是空,可以认为lmax2 = float('-inf')
    rmin2 = float('inf') if c2 == 2 * n else nums2[c2 // 2],割在数组2的开头,割的左边是空,可以认为rmin2 = float('inf')
class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        #划分数组
        m,n = len(nums1),len(nums2)
        if m > n:		#保证数组1一定最短
            return self.findMedianSortedArrays(nums2,nums1)
        #Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
        left ,right = 0,2*m #虚拟加了'#'所以数组1是2*n长度
        while left <= right:
            c1 = left + (right - left) // 2  #c1是二分的结果
            c2 = m + n - c1
            lmax1 = float('-inf') if c1 == 0 else nums1[ (c1 - 1) // 2]
            rmin1 = float('inf') if c1 == 2*m else nums1[c1 // 2]
            lmax2 = float('-inf') if c2 == 0 else nums2[(c2 - 1) // 2]
            rmin2 = float('inf') if c2 == 2*n else nums2[c2 // 2]
            if lmax1 > rmin2: #c1向左二分
                right = c1 - 1
            elif lmax2 > rmin1: #c1向右二分
                left = c1 + 1
            else:
                break
        return (max(lmax1,lmax2),min(rmin1,rmin2)) / 2

C++实现:

//在自己的编译器中,加头文件很重要
#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        int m = nums2.size();

        if (n > m)  //保证数组1一定最短
        {
            return findMedianSortedArrays(nums2, nums1);
        }

        // Ci 为第i个数组的割,比如C1为2时表示第1个数组只有2个元素。LMaxi为第i个数组割后的左元素。RMini为第i个数组割后的右元素。
        int LMax1, LMax2, RMin1, RMin2, c1, c2, lo = 0, hi = 2 * n;  //我们目前是虚拟加了'#'所以数组1是2*n长度

        while (lo <= hi)   //二分
        {
            c1 = (lo + hi) / 2;  //c1是二分的结果
            c2 = m + n - c1;

            LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
            RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
            LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
            RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];

            if (LMax1 > RMin2)
                hi = c1 - 1;
            else if (LMax2 > RMin1)
                lo = c1 + 1;
            else
                break;
        }
        return (max(LMax1, LMax2) + min(RMin1, RMin2)) / 2.0;
    }
};

int main(int argc, char *argv[])
{
   vector<int> nums1 = { 2,3, 5 };
   vector<int> nums2 = { 1,4,7, 9 };

   Solution solution;
   double res = solution.findMedianSortedArrays(nums1, nums2);
   cout << res <<endl;
   return 0;
}

3、复杂度分析:

方法1:
时间复杂度:O(M+N),M、N分别为两个数组的长度
空间复杂度:O(M+N)
方法2:
时间复杂度:O(M+N),M、N分别为两个数组的长度
空间复杂度:O(1)

方法3:
时间复杂度:O(log(M+N)),M、N分别为两个数组的长度
空间复杂度:O(1)
方法4:
时间复杂度:O(log(min(M,N)))
空间复杂度:O(1)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值