算法题[leetcode] 寻找两个正序数组的中位数【困难题目】 python&java解法

leetcode算法题:寻找两个正序数组的中位数 java&python解法

题目概述:

在这里插入图片描述
在这里插入图片描述

解题思路:

首先,题目是两个有序数组,然后寻找这两个数组中的中位数,我们如果用手算的话,肯定是把两个数组合并成一个,然后算其中的中位数。这是比较简单的解法,但是这道题是困难题目啊,不可能说用双指针合并一下就算完成任务了,这样并没有达到题目要求的log级的时间复杂度。但是不管怎么样,有解法总比没头绪好,我先从O(m+n)级别的时间复杂度来解这道题。

双指针遍历合并数组取中位数 思路:

1.如何合并数组?由于两个数组是从小到大有序的,我们需要将他们合并成一个同样有序的数组,双指针的方法就很方便了,我们可以定义两个指针,分别指向数组的第一个元素,比较两个指针所指向的值的大小,将值小的呢个数放进新的数组里,然后把当前指针后移一位,继续通过while循环进行比较。
2.while循环的终止条件?我们既然使用双指针遍历,指针当然不能越界,这就是中止条件,其中如果出现一个数组遍历完了,另外一个数组还有元素,我们只需要把剩下的元素放进新数组里就可以了。所以中止条件就是两个指针不能越界,而且是或的关系。

3.如何在新数组中找到中位数?这就很简单了,判断数组长度是奇数还是偶数个,如果是奇数,就取length/2呢个,如果是偶数,就取中间两个/2就ok了

这个算法简单易懂,但是时间复杂度达到O(m+n),由于我们申请了新数组,空间复杂度也达到了O(n),没有达到题目的要求,但是我们可以再此代码上优化,将空间复杂度降到O(1),也就是我后序要说的改进算法。

双指针代码比较简单,python代码我就不提供了,如果有需要可以评论,我会及时更新。

java代码实现 双指针遍历:
 public static  double findMedianSortedArrays(int [] nums1,int[] nums2) {
        //定义双指针
        int point1 = 0;
        int point2 = 0;
        //创建新数组
        ArrayList<Integer> result = new ArrayList<Integer>();
        //双指针遍历
        while (point1 < nums1.length || point2 < nums2.length) {
            //一个数组遍历结束,另一个数组还有元素
            if (point1 == nums1.length) {
                result.add(nums2[point2++]);
                continue;
            }
            if (point2 == nums2.length) {
                result.add(nums1[point1++]);
                continue;
            }
            //将较小的值放入新数组
            if (nums1[point1] <= nums2[point2]) {
                result.add(nums1[point1++]);
            } else {
                result.add(nums2[point2++]);
            }
        }
        //新数组的长度
        int length = result.size();
        //取中位数
        if (length < 2) {
            return length == 0 ? 0 : result.get(0);
        }
        if (length % 2 == 0) {
            return (double) (result.get(length / 2 - 1) + result.get(length / 2)) / 2;
        }
            return result.get((length + 1) / 2 - 1);
    }

上述已经提到,此算法的时间复杂度和空间复杂度对于这道题目来说都比较高,所以我们可以对此代码进行优化,因为双指针算法无法达到log级,呢我们就优化空间复杂度到O(1)

双指针算法优化思路:

1.我们如果要降低空间复杂度到O(1),呢就不需要取创建新数组,我们既然有两个指针去遍历,呢我们用变量去存储较小的值就好了。

2.我们也不需要去遍历完整的两个数组,因为是有序的,我们只需要遍历两个数组的长度之和/2就好了,遍历完成之后,我们用来存储值的变量就是我们需要的中位数。

3.偶数长度有两个值怎么办?我们可以使用两个变量去存储值,left和right 用right去存储较小的值,每次遍历都将right赋值给left,这样的话,如果有两个值,就一定是left和right。

这样的话我们只申请了几个变量,空间复杂度位常数阶O(1) ,时间复杂度虽然缩短了一半为O((m+n)/2+1)但同样还是O(m+n)。如何才能达到题目要求的log级别呢?我们首先就应该想到的是二分法,通过每次排除掉一半的元素 实现log(m+n)的时间复杂度,后面会有二分法的解法

java代码实现 双指针遍历优化:
  public static  double findMedianSortedArrays(int [] nums1,int[] nums2) {
        //定义双指针
        int point1 = 0;
        int point2 = 0;
        //两个变量用来存储值
        int left = 0;
        int right= 0;
        //两个数组长度
        int length = nums1.length+nums2.length;
        //遍历length/2次
        for (int i = 0; i <= length/2 ; i++) {
            //更新left的值
            left = right;
            //以下的代码就和双指针大同小异了,就是把较小的值用right存起来
            if (point1==nums1.length){
                right = nums2[point2++];
                continue;
            }
            if (point2==nums2.length){
                right = nums1[point1++];
                continue;
            }
            if (nums1[point1]<=nums2[point2]){
                right = nums1[point1++];
            }else {
                right = nums2[point2++];
            }
        }
        //判断长度奇偶性
        if (length %2 == 0){
            return (double)(left+right)/2;
        }
        return right;
        }

Python代码实现 双指针遍历优化:
def findMedianSortedArrays(nums1: list, nums2: list) -> float:
    # 定义双指针
    point1 = 0
    point2 = 0
    # 两个变量存值
    left = 0
    right = 0
    # 长度和
    length = len(nums1) + len(nums2)
    # 遍历length//2次
    for _ in range(length // 2 + 1):
        # 更新left的值
        left = right
        # 将较小的值存到right中
        if point1 == len(nums1):
            right = nums2[point2]
            point2 += 1
            continue
        if point2 == len(nums2):
            right = nums1[point1]
            point1 += 1
            continue
        if nums1[point1] <= nums2[point2]:
            right = nums1[point1]
            point1 += 1
        else:
            right = nums2[point2]
            point2 += 1
    # 取中位数
    if length % 2 == 0:
        return (left + right) / 2
    return right

二分法 思路:

题目中要求时间复杂度达到log级,我们就用二分法来解决它。核心问题就是怎么进行二分?从哪里分割?

根据题意,我们需要找到中位数,换句话说,我们需要找到有序数组中第 length/2 小的元素。两者同理,这样的话,我们可以进行二分了,我通俗易懂的讲,如果两个数组的长度都为6,一共长度为12,我们要找第12/2 = 6小的元素(先不考虑奇数长度),呢我们就可以比较两个数组的第 6/2=3个元素,为什么要取3呢?

我们来举个例子

两个数组 list1 = [1,2,3,7,8,9] list2 = [4,5,6,7,8,9]

我们把3和6比较,3<6 我们就可以把 list1中的1,2,3直接排除掉,因为3不可能是第6小的元素,3比6小,就算是极端情况,6之前的两个位置都比3小,3也是第5小的元素,所以可以直接把1,2,3排除掉。

更精确的来算的话,比如说我们要找第x小的元素,我们比较的就是list1[x/2]和list2[x/2]两个元素,如果list1[x/2]比较小,这两个元素前面各有x/2-1个元素,x/2-1+x/2-1 = x-2 我们需要找的是第x小的元素,但前面只有x-2个元素,加上list1[x/2]也才x-1个元素,所以list1前x/2个元素都可以排除掉。因为排除了一部分元素,需要把x做调整,x = x-被排除的元素个数 。然后我们再重新二分,再将x/2,将较小的元素以及它前面的元素剔除掉,直到x/2=1 我们需要找第1小的元素为止。 需要注意的是一些细节处理很重要!

我在此提供递归和非递归的两种代码,分别用python和java实现

java代码实现 正常二分法 非递归:
public static  double findMedianSortedArrays(int [] nums1,int[] nums2) {
    int length = nums1.length + nums2.length;
    //如果是奇数,则只需要找到第length/2+1小的数
    if (length % 2 == 1) {
        int needEle = length / 2;
        return getX(nums1, nums2, needEle + 1);
    }
    // 偶数需要找到 第 length/2+1 和 length/2 小的数
    int needEle = length / 2 - 1, midIndex2 = length / 2;
    return (getX(nums1, nums2, needEle + 1) + getX(nums1, nums2, midIndex2 + 1))/2.0;

}
    public static int getX(int[] nums1, int[] nums2, int x) {
        //定义双指针
        int point1 = 0;
        int point2 = 0;
        // while循环二分
        while (true) {
           //终止条件
            if (x == 1) {
                return nums1[point1]>nums2[point2]?nums2[point2]:nums1[point1];
            }
            // 我们需要处理一些极端边界条件
            if (point1 == nums1.length) {
                return nums2[point2 + x - 1];
            }
            if (point2 == nums2.length) {
                return nums1[point1 + x - 1];
            }
            //二分,如果x/2大于数组长度,我们取数组长度即可
            int compare1 = point1 + x / 2 > nums1.length ? nums1.length : point1 + x / 2;
            int compare2 = point2 + x / 2 > nums2.length ? nums2.length : point2 + x / 2;
            //比较两个数,将小的元素 之前的元素全部排除,调整x的值
            if (nums1[compare1] <= nums2[compare2]) {
                x -= (compare1 - point1 + 1);
                point1 = compare1 + 1;
            } else {
                x -= (compare2 - point2 + 1);
                point2 = compare2 + 1;
            }
        }
    }
python代码实现 二分法+递归
def findMedianSortedArrays(self, nums1: list, nums2: list) -> float:

    # 我们可以把奇数和偶数情况合并,也就说都要找两次,只不过奇数情况是找两个同样的数
    left = (len(nums1) + len(nums2) + 1) // 2
    right = (len(nums1) + len(nums2) + 2) // 2

    def get_x(nums1, nums2, x):
        # 保证如果有空数组一定是nums1,且保证nums1最短
        if len(nums1) > len(nums2):
            return get_x(nums2,nums1,x)
        # 边界条件处理
        if len(nums1) == 0:
            return nums2[x - 1]
        # 终止条件处理
        if x == 1:
            return min(nums1[0], nums2[0])
        # 保证索引不越界
        compare = min(x // 2, len(nums1))
        if nums1[compare - 1] <= nums2[compare - 1]:
            return get_x(nums1[compare:], nums2, x - compare)
        else:
            return get_x(nums1, nums2[compare:], x - compare)

    return (get_x(nums1, nums2, left) + get_x(nums1, nums2, right)) / 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值