记录这几天刷题做到的一个数组的算法题的过程:

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

算法的时间复杂度应该为 O(log (m+n))

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/...
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
  
    }

分析

此题目在力扣的难度为困难,乍一看题目并不是很难,用数组合并的笨方法就可以解决。

真正的难点在于时间复杂度是 Log 级别

我们先来看一下不要求的时间复杂度的情况下如何处理。

奇偶判断

如果总数是奇数,返回中间值,如果总数是偶数,返回中间值和中间值+1(由于给出的数据结构是数组,所有的下标-1)

如果不想用 IF 实现判断,也可以使用条件值的判断,如:

return ( nums2_length % 2 == 0 ) ?  // 是0吗?
       ( nums2[middle - 1] + nums2[middle] ) / 2.0 :  // 如果是0说明是偶数,返回中间两个树的平均值
       nums2[middle];    // 如果不是0说明是奇数,返回中间的数

边界条件

首先考虑边界条件,题目的输入可能会出现一个数组为空的情况,如:

输入:nums1 = [1,3], nums2 = []
输入:nums1 = [], nums2 = [2,3,4]

这种情况单独加一个判断,如果有一个数组为空,就直接用下标求出另一个数组的中位数并直接返回,不需要合并数组:

// 长度
int nums1_length = nums1.length;
int nums2_length = nums2.length;
int total_length = nums1_length + nums2_length;
// 如果数组1为空,求数组2的中位数
if (nums1_length == 0) {
    int middle = nums2_length / 2;
    return ( nums2_length % 2 == 0 ) ?
           ( nums2[middle - 1] + nums2[middle] ) / 2.0 :
           nums2[middle];
}
// 如果数组2为空,求数组1的中位数
if (nums2_length == 0) {
    int middle = nums1_length / 2;
    return ( nums1_length % 2 == 0 ) ?
           ( nums1[middle - 1] + nums1[middle] ) / 2.0 :
           nums1[middle];
}   

一般情况

在两个数组都非空的情况下,尝试合并数组,然后取最终数组的中位数

首先定义了三个索引,分别是合并后的索引,nums1的索引,nums2的索引,然后创建一个永真循环:

int[] nums = new int[total_length];
// i:合并后的列表的索引,j:nums1的索引,k:nums2的索引
int i = 0, j = 0, k = 0;
//
while (true) {

}

接下来看几种情况:

如果 nums1的索引等于它的长度,说明这个数组循环结束了,只把 nums2拼接到合并后数组的后面即可

    if (j == nums1_length && k != nums2_length) {
        nums[i] = nums2[k];
        k++;
        i++;
    }

    else if (j != nums1_length && k == nums2_length) {
        nums[i] = nums1[j];
        j++;
        i++;
    }

如果两个数组的索引都等于他们的长度,说明循环结束,跳出循环,返回合并后数组的中位数

    else if (j == nums1_length && k == nums2_length) {
        int middle = total_length / 2;
        return ( total_length % 2 == 0 ) ?
               ( nums[middle - 1] + nums[middle] ) / 2.0 :
               nums[middle];
    }

其他情况就是正常的情况了,由于原有数组是升序,合并后的数组也是升序, 也就是插入每次比较时更小的那个

    else {
        if (nums1[j] > nums2[k]) {
            nums[i] = nums2[k];
            k++;
        } else {
            nums[i] = nums1[j];
            j++;
        }
        i++;
    }

小结

整个流程就变成了:

①看是否有一个数组是空,如果有,直接返回另一个数组的中位数

②开始循环,每次循环验证是否有数组的索引等于数组长度,
如果一个数组满足,说明这个数组循环完毕,直接把另一个数组的尾部插入到合并后数组的尾部
如果两个数组满足,说明循环结束,返回合并后数组的中位数
如果没有数组满足,进入③

③正常情况下:比较两个数,插入更小的数字,进行下一次循环

最后发现这个算法时间复杂度是 O(m+n),空间复杂度 O(m+n)

完整代码:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 长度
        int nums1_length = nums1.length;
        int nums2_length = nums2.length;
        int total_length = nums1_length + nums2_length;
        //
        if (nums1_length == 0) {
            int middle = nums2_length / 2;
            return ( nums2_length % 2 == 0 ) ?
                   ( nums2[middle - 1] + nums2[middle] ) / 2.0 :
                   nums2[middle];
        }
        if (nums2_length == 0) {
            int middle = nums1_length / 2;
            return ( nums1_length % 2 == 0 ) ?
                   ( nums1[middle - 1] + nums1[middle] ) / 2.0 :
                   nums1[middle];
        }
        // 定义数组
        int[] nums = new int[total_length];
        // i:合并后的列表的索引,j:nums1的索引,k:nums2的索引
        int i = 0, j = 0, k = 0;
        //  开始循环
        while (true) {
            // 数组1循环结束
            if (j == nums1_length && k != nums2_length) {
                nums[i] = nums2[k];
                k++;
                i++;
            }
            // 数组2循环结束
            else if (j != nums1_length && k == nums2_length) {
                nums[i] = nums1[j];
                j++;
                i++;
            }
            // 整体循环结束
            else if (j == nums1_length && k == nums2_length) {
                int middle = total_length / 2;
                return ( total_length % 2 == 0 ) ?
                       ( nums[middle - 1] + nums[middle] ) / 2.0 :
                       nums[middle];
            }
            // 一般情况
            else {
                if (nums1[j] > nums2[k]) {
                    nums[i] = nums2[k];
                    k++;
                } else {
                    nums[i] = nums1[j];
                    j++;
                }
                i++;
            }
        }
        return 0;
    }
}

初步优化

在不改变整体思路的情况下,优化思路就是

①不循环整个数组,而是索引达到中位数就停止循环,这样循环次数减少一半,但复杂度仍是 O(m+n)

②不记录所有的合并结果,而是只保留几个固定元素,达到中位数就停止循环,这样空间复杂度能降到O(1)

但这样还是无法满足题目要求,所以只能把整个思路都推翻掉,合并数组的方案在一开始就是不满足要求的。

改变思路——二分法

这个思路是真正的解法,但目前看起来比较难
暂时附上链接:https://leetcode.cn/problems/...

到笔者汇报的时候还没完全看懂,后面再补充


LYX6666
1.6k 声望73 粉丝

一个正在茁壮成长的零基础小白