【无标题】寻找两个正序数组的中位数

3种方法解决中位数问题:

1.常规思想的改进: 假合并/奇偶合并

本题的常规思想还是挺简单的: 使用归并的方式, 合并两个有序数组, 得到一个大的有序数组. 大的有序数组的中间位置的元素, 即为中位数. 但是这种思路的时间复杂度是 O(m+n), 空间复杂度是 O(m+n)不符理想化!

因此我们必须想办法将算法进行优化, 这里先介绍一种简单的优化方式, 就是 **假合并**, 即我们并不需要真的合并两个有序数组, 只要找到中位数的位置即可.

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

通过这种 假合并 的方式, 我们可以成功的将空间复杂度优化到了O(1), 但是对于时间复杂度并没有什么优化. 此方法理解是比较容易的, 但是真正写代码时候还是很有挑战的, 你不仅要考虑**奇偶的问题**, 更要考虑 一个数组遍历结束后 的各种边界问题, 其实很多困难题就是难在了对于边界的处理上面了.

这种思想是很有必要的, 对于数组来说, 我们经常会遇到奇偶的两种情况处理, 如果想办法将他们合并在一起, 那代码写起来就是非常顺畅和整洁.

另一种合并的思想是: 我们可以在奇数的时候, 在**末尾等处添加一个占位符**等, 这样也是可以将奇数合并成偶数的情况的.

此方法的另一个优化点就是 通过在if条件中**加入大量的限制条件**, 从而实现了对于各种边界问题的处理, 这也是一种很重要的思想.

2.寻找第k小数 代码详解

关于本题转换为 **第k小数** 的思想, 就不用纠结怎么想到的了, 大家就安心的理解思想和代码并将它记在脑中就可以了.

其实关于这个算法的思想并不是太难理解, 主要就是根据两个数的三种比较结果, **不断地去除不满足的元素**的过程

我认为这个思想最难的点在于 **三种特殊情况的处理**, 我们能否想到这三种情况, 并将他们**完美的融入到代码之中**, 我感觉这才是真正的难点所在.

接下来我们来详细解读此思想的代码实现.

最开始对于奇数和偶数的两种情况进行了判断, 其实是可以将两种情况合并的, 只需要在奇数时求两次同样的k就可以了.

接下来处理了三种特殊情况中的两种特殊情况: 一个数组为空 和 k=1.

下面的**几个定义**就非常重要了, 一定要弄清这些定义的含义, 才能更轻松的理解代码.

index1, index2作为数组的**起始点的下标**, 初值都是0, 但是随着两个数组不断被删除元素, 这两个起始点也是在不断的进行变化, 具体变化方式就是 **index1 = newIndex1 + 1**, 因为在删除元素的时候 **连同比较位置也一同删去了**, 所以新的开始是 比较位置 的后一位.

newindex1, newindex2作为比较点就是图中**被框中的两个数的下标**, 它的赋值过程就涉及到了 **最后一个边界情况**. 因为当一个数组较短时, 其中一个比较点可能已经到达了数组的最后, 所以它的值是 **两种情况下较小的那个数**.

接下来就是根据两个比较点的大小来进行不同的操作过程了, 这里最难理解的点就是 **k -= (newIndex1 - index1 + 1)**, 也就是**减去元素的个数问题**了. 我们根据上面的图来举例, 图中index1的值为0, newindex1的值经过计算为1, 通过比较后, 可以看到 红色的数 就是被删除的数, 也就是两个, 所以我们需要在最后+1才是真实被删去的个数. 对于此类问题在确定最终个数的时候, 我们都可以通过这样的**特例来决定代码的书写**, 至此代码就全部讲解完成了.

## 3.理解中位数作用进行 划分数组

最后这种思想的时间复杂度甚至比上面的还低, 上面的思想每一轮循环可以将查找范围减少一半,因此时间复杂度是O(log(m+n)), 但这种思想可以对确定的较短的数组进行二分查找, 所以它的时间复杂度是 O(log min(m,n)).

划分数组 正好和上面算法完全相反, 它的**思想特别复杂**, 但思想理解了, 代码写起来倒是没太大的难度, 所以我们重点说说它的思想.

首先我们要明白**中位数的作用**: 将一个集合划分为两个长度相等的子集, 其中一个子集中的元素总是大于另一个子集中的元素, 这种思想无论是在几个数组中都是适用的, 这就衍生出了下面的算法思想.

首先来讨论奇偶的两种不同情况下的不同划分方式

然后在编写代码的时候, 由于计算机的**取整操作**, 我们是可以将这两种情况合并成一种代码书写方式的. 其中的i和j分别是两个数组的划分位置.

同样我们也会遇到复杂的边界问题, 但下面这种处理方式是真的非常优秀.

上面问题都考虑完了, 其实就可以写代码了, 但是我们需要进行两个条件的判断: B[j−1]≤A[i] 以及A[i−1]≤B[j], 为了优化代码, 经过分析后, 我们发现这两种情况是可以**等价转换**的. 也就是只需要进行一个条件的判断即可.

相关运行代码,部分讲述在代码中已经讲清。

class Solution {

    public double findMedianSortedArrays(int[] nums1, int[] nums2) {

        int len = nums1.length + nums2.length;

        if(len % 2 == 0){

            return (getKNums(nums1, nums2, len / 2) + getKNums(nums1, nums2, len / 2 + 1)) / 2.0;

        }else{

            return getKNums(nums1, nums2, len / 2 + 1);

        }

    }

    private int getKNums(int[] nums1, int[] nums2, int k) {

        // 1 2 4 6 8

        // 2 3 4 9 10

        // 求第k大,主要思想就是通过二分削减不可能参与第k大筛选的序列。

        // 比如上面的序列求第8大,则8/2=4,6<9,那么6以及之前的序列1 2 4 6不可能有第8大。

        // 通过这种比较确定不了到底哪个是第8大,因为8后面是6后面还可能有别的数(比如8),这也可能是第八大

        // 因此只能排除,不能确定。如果要确定的话,得憋到k=1,这就是返回条件. 当然,另一组已经溢出也可以直接获得结果。

        if(nums1.length == 0){

            return nums2[k - 1];

        }

        if(nums2.length == 0)

            return nums1[k - 1];

        int i = -1, j = -1;

        while(k > 0) {

            if(i >= nums1.length - 1){

                return nums2[j + k];

            }

            if(j >= nums2.length - 1)

                return nums1[i + k];

            if(k == 1) {

                if( i + 1 >= nums1.length)

                    return nums2[j + 1];

                else if (j + 1 >= nums2.length){

                    return nums1[i + 1];

                }else {

                    return Math.min(nums2[j + 1], nums1[i + 1]);

                }

            }

            int inc1 = Math.min(nums1.length - i - 1, k/2);

            int inc2 = Math.min(nums2.length - j - 1, k/2);

            int cmp = nums1[i + inc1] - nums2[j + inc2];

            if(cmp < 0) {

                i += inc1;

                k -= inc1;

            }else {

                j += inc2;

                k -= inc2;

            }

        }

        return 0;

    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值