LeetCode 4 寻找两个正序数组的中位数

题目描述

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

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

示例 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

提示:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -106 <= nums1[i], nums2[i] <= 106

解法

  1. 遍历解法

思路:

  • 先找到中位数在哪
  • 依次遍历两个数组,依次从两个数组前面取元素(都是数组最小的元素),比较大小,取较小的,
  • 更新元素小的那个数组的索引,向后移动一位,继续遍历比较
  • 只需要遍历到中位数次数即可。

java代码:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        // 先找到计算中位数的位置:如果是奇数则去中间的,否则取中间两个的平均数
        ArrayList<Integer> arr = new ArrayList<>();
        if ((nums1.length + nums2.length) % 2 == 1) {
            arr.add((nums1.length + nums2.length) / 2 + 1);
        } else {
            arr.add((nums1.length + nums2.length) / 2);
            arr.add((nums1.length + nums2.length) / 2 + 1);
        }
        // 中位数的和
        int sum = 0;


        int index1 = 0;  // 数组1的索引
        int index2 = 0;  // 数组2的索引
        int count = 0;   // 已经排序的总数
        // 遍历两个数组,依次从两个数组取元素,比较大小,取较小的
        while (count < (nums1.length + nums2.length) / 2 + 1) {
            // 比较两个数组的元素,更新元素小的那个数组的索引,并取出小的元素
            int min;
            if (index1 >= nums1.length) {
                // 说明nums1数组被取完了,去取nums2数组
                min = nums2[index2];
                index2 ++;
            } else if (index2 >= nums2.length) {
                // 说明nums2数组被取完了,去取nums1数组
                min = nums1[index1];
                index1 ++;
            } else if (nums1[index1] < nums2[index2]) {
                // nums1数组的元素更小
                min = nums1[index1];
                index1 ++;
            } else {
                // nums2数组的元素更小
                min = nums2[index2];
                index2 ++;
            }
            
            count ++;
            if (arr.contains(count)) {
                // 计算平均数的和
                sum += min;
            }
        }

        return (double)sum / arr.size();
    }
}

复杂度

时间复杂度:O(m+n),m和n分别为数组长度
空间复杂度:O(1)

  1. 二分 + 递归

思路:

  • 找第k大的数,那么每次先去排除k/2个数;
  • 分别两个数组里找第k/2的数,如果 k 是奇数,向下取整,然后比较两个数组的第k/2的数大小,小的那个数组的第k/2的数前面的数,肯定不是第K大的数;
  • 把小的数组的前面的数排除掉(k/2个),因为前面已经排除了k/2个数,所有后面只在两个数组里找第 k- k/2 大的数即可

注意特殊情况(递归终止条件):

  1. 每次都是取 k/2 的数进行比较,有时候可能会遇到数组长度小于 k/2的时候,那么只要取末尾那个数就行
  2. 如果有数组为空,则只取不为空的数组中位数

java代码:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int len1 = nums1.length;
        int len2 = nums2.length;
        int sumLen = len1 + len2;
        if (sumLen % 2 == 1) {  // 奇数个
             return getKth(nums1, nums2, 0, len1 - 1, 0, len2 - 1, sumLen / 2 + 1);
        } else {  // 偶数个
            int mid1 = getKth(nums1, nums2, 0, len1 - 1, 0, len2 - 1, sumLen / 2);
            int mid2 = getKth(nums1, nums2, 0, len1 - 1, 0, len2 - 1, sumLen / 2 + 1);
            return (mid1 + mid2) / 2.0d;
        }
        // 时间复杂度是O(log(k)),因为k = (m + n)/2 ,所以时间复杂度O(log(m + n))
    }

    /**
     * 辅助函数:找两个数组第k大的数
     * 二分 + 递归
     * 整体思路:找第k大的数,那么每次先去排除k/2个数
     *          分别两个数组里找第k/2的数,如果 k 是奇数,向下取整,然后比较两个数组的第k/2的数大小,小的那个数组的第k/2的数前面的数,肯定不是第K大的数
     *          把小的数组的前面的数排除掉(k/2个),因为前面已经排除了k/2个数,所有后面只在两个数组里找第 k- k/2 大的数即可
     *          特殊情况:1. 每次都是取 k/2 的数进行比较,有时候可能会遇到数组长度小于 k/2的时候,那么只要取末尾那个数就行
     *                   2.如果有数组为空,则只取不为空的数组中位数
     *
     * @param nums1  数组1
     * @param nums2  数组2
     * @param start1 数组1的起始索引
     * @param end1  数组1的结束索引
     * @param start2  数组2的起始索引
     * @param end2  数组2的结束索引
     * @param k
     * @return
     */
    private static int getKth(int[] nums1, int[] nums2, int start1, int end1, int start2, int end2, int k) {
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;

        // 让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1
        if (len1 > len2) {
            return getKth(nums2, nums1, start2, end2, start1, end1, k);
        }

        // 下面是递归的两个结束条件
        // 1. 如果第一个数组长度为0,直接找第二个数组中位数即可
        if (len1 == 0) {
            return nums2[start2 + k -1];
        }
        // 2. 如果k等于1,直接找两个数组中第一个元素小的那个元素
        if (k == 1) {
            return Math.min(nums1[start1], nums2[start2]);
        }

        // 找两个个数组的第k/2个索引
        // 小心这里可能数组越界,即第一个数组的长度没有第k/2个数大了,那么就取最后一个
        int index1 = start1 + Math.min(len1, k/2) -1;
        int index2 = start2 + Math.min(len2, k/2) -1;

        // 比较两个数组索引位置的元素大小,小的那个前面的元素不要了,继续递归调用找第k- k/2 大的数
        // 注意点:这里的第k- k/2 大的,需要考虑到上面可能第一个数组的长度没有第k/2个数大了
        // 那么就只排除了第一个数组长度的数,所以,不应该找第k- k/2 大的数,而是找第k- num1.length 大的数,
        // 两种情况综合考虑,即找第 k - Math.min(len2, k/2)大的数,可用k - (index1 - start1 + 1)表示
        if (nums1[index1] < nums2[index2]) {
            return getKth(nums1, nums2, index1 + 1, end1, start2, end2, k - (index1 - start1 + 1));
        } else {
            return getKth(nums1, nums2, start1, end1, index2 + 1, end2, k - (index2 - start2 + 1));
        }

    }
}

复杂度

时间复杂度:O(log(m + n)),时间复杂度是O(log(k)),因为k = (m + n)/2 ,所以时间复杂度O(log(m + n))
空间复杂度:O(1)

你可能感兴趣的:(leetcode,leetcode,算法,java)