Skip to content

Latest commit

 

History

History
199 lines (156 loc) · 9.1 KB

File metadata and controls

199 lines (156 loc) · 9.1 KB

English Version

题目描述

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

算法的时间复杂度应该为 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

解法

方法一:分治

本题限制了时间复杂度为 $O(\log (m+n))$,看到这个时间复杂度,自然而然的想到了应该使用二分查找法来求解。那么回顾一下中位数的定义,如果某个有序数组长度是奇数,那么其中位数就是最中间那个,如果是偶数,那么就是最中间两个数字的平均值。这里对于两个有序数组也是一样的,假设两个有序数组的长度分别为 $m$$n$,由于两个数组长度之和 $m+n$ 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。为了简化代码,不分情况讨论,我们使用一个小 trick,我们分别找第 $\frac{m+n+1}{2}$$\frac{m+n+2}{2}$ 个,然后求其平均值即可,这对奇偶数均适用。假如 $m+n$ 为奇数的话,那么其实 $\frac{m+n+1}{2}$$\frac{m+n+2}{2}$ 的值相等,相当于两个相同的数字相加再除以 2,还是其本身。

这里我们需要定义一个函数来在两个有序数组中找到第 $k$ 个元素,下面重点来看如何实现找到第 $k$ 个元素。

首先,为了避免产生新的数组从而增加时间复杂度,我们使用两个变量 $i$$j$ 分别来标记数组 nums1nums2 的起始位置。然后来处理一些边界问题,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果 $k=1$ 的话,那么我们只要比较 nums1nums2 的起始位置 $i$$j$ 上的数字就可以了。

难点就在于一般的情况怎么处理?因为我们需要在两个有序数组中找到第 $k$ 个元素,为了加快搜索的速度,我们要使用二分法,对 $k$ 二分,意思是我们需要分别在 nums1nums2 中查找第 $\left \lfloor \frac{k}{2} \right \rfloor$ 个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字,所以我们需要先检查一下,数组中到底存不存在第 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字,如果存在就取出来,否则就赋值上一个整型最大值。如果某个数组没有第 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字,那么我们就淘汰另一个数字的前 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字即可。有没有可能两个数组都不存在第 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字呢,这道题里是不可能的,因为我们的 $k$ 不是任意给的,而是给的 $m+n$ 的中间值,所以必定至少会有一个数组是存在第 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字的。

最后是二分法的核心,比较这两个数组的第 $\left \lfloor \frac{k}{2} \right \rfloor$ 小的数字 midVal1midVal2 的大小,如果第一个数组的第 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字小的话,那么说明我们要找的数字肯定不在 nums1 中的前 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字,所以我们可以将其淘汰,将 nums1 的起始位置向后移动 $\left \lfloor \frac{k}{2} \right \rfloor$ 个,并且此时的 $k$ 也自减去 $\left \lfloor \frac{k}{2} \right \rfloor$,调用递归。反之,我们淘汰 nums2 中的前 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字,并将 nums2 的起始位置向后移动 $\left \lfloor \frac{k}{2} \right \rfloor$ 个,并且此时的 $k$ 也自减去 $\left \lfloor \frac{k}{2} \right \rfloor$,调用递归即可。

实际是比较两个数组中的第 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字哪一个可能到达最后合并后排序数组中的第 $k$ 个元素的位置,其中小的那个数字注定不可能到达,所以可以直接将小的元素对应的数组的前 $\left \lfloor \frac{k}{2} \right \rfloor$ 个数字淘汰。

时间复杂度 $O(\log (m+n))$,其中 $m$$n$ 是两个数组的长度。

Python3

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        def findKth(i, j, k):
            if i >= m:
                return nums2[j + k - 1]
            if j >= n:
                return nums1[i + k - 1]
            if k == 1:
                return min(nums1[i], nums2[j])
            midVal1 = nums1[i + k // 2 - 1] if i + k // 2 - 1 < m else inf
            midVal2 = nums2[j + k // 2 - 1] if j + k // 2 - 1 < n else inf
            if midVal1 < midVal2:
                return findKth(i + k // 2, j, k - k // 2)
            return findKth(i, j + k // 2, k - k // 2)

        m, n = len(nums1), len(nums2)
        left, right = (m + n + 1) // 2, (m + n + 2) // 2
        return (findKth(0, 0, left) + findKth(0, 0, right)) / 2

Java

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
    }

    private int findKth(int[] nums1, int i, int[] nums2, int j, int k) {
        if (i >= nums1.length) {
            return nums2[j + k - 1];
        }
        if (j >= nums2.length) {
            return nums1[i + k - 1];
        }
        if (k == 1) {
            return Math.min(nums1[i], nums2[j]);
        }
        int midVal1 = (i + k / 2 - 1 < nums1.length) ? nums1[i + k / 2 - 1] : Integer.MAX_VALUE;
        int midVal2 = (j + k / 2 - 1 < nums2.length) ? nums2[j + k / 2 - 1] : Integer.MAX_VALUE;
        if (midVal1 < midVal2) {
            return findKth(nums1, i + k / 2, nums2, j, k - k / 2);
        }
        return findKth(nums1, i, nums2, j + k / 2, k - k / 2);
    }
}

C++

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        int left = (m + n + 1) / 2;
        int right = (m + n + 2) / 2;
        return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
    }

    int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) {
        if (i >= nums1.size()) return nums2[j + k - 1];
        if (j >= nums2.size()) return nums1[i + k - 1];
        if (k == 1) return min(nums1[i], nums2[j]);
        int midVal1 = i + k / 2 - 1 < nums1.size() ? nums1[i + k / 2 - 1] : INT_MAX;
        int midVal2 = j + k / 2 - 1 < nums2.size() ? nums2[j + k / 2 - 1] : INT_MAX;
        if (midVal1 < midVal2) return findKth(nums1, i + k / 2, nums2, j, k - k / 2);
        return findKth(nums1, i, nums2, j + k / 2, k - k / 2);
    }
};

Go

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
	m, n := len(nums1), len(nums2)
	left, right := (m+n+1)/2, (m+n+2)/2
	var findKth func(i, j, k int) int
	findKth = func(i, j, k int) int {
		if i >= m {
			return nums2[j+k-1]
		}
		if j >= n {
			return nums1[i+k-1]
		}
		if k == 1 {
			return min(nums1[i], nums2[j])
		}
		midVal1 := math.MaxInt32
		midVal2 := math.MaxInt32
		if i+k/2-1 < m {
			midVal1 = nums1[i+k/2-1]
		}
		if j+k/2-1 < n {
			midVal2 = nums2[j+k/2-1]
		}
		if midVal1 < midVal2 {
			return findKth(i+k/2, j, k-k/2)
		}
		return findKth(i, j+k/2, k-k/2)
	}
	return (float64(findKth(0, 0, left)) + float64(findKth(0, 0, right))) / 2.0
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

...