博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【LeetCode】004:寻找两个正序数组的中位数
阅读量:2054 次
发布时间:2019-04-28

本文共 12133 字,大约阅读时间需要 40 分钟。

记录一下自己的练习。


题目

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


示例

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

输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

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

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

输入:nums1 = [], nums2 = [1]

输出:1.00000


方法

方法一:合并数组

解题思路

  1. 只要找到中位数的位置即可。由于两个数组的长度已知,因此中位数对应的两个数组的下标之和也是已知的。假设两个数组的长度分别为 m m m, n n n,如果 m + n m+n m+n 为 奇数时,中位数是两个有序数组中的下标为 ( m + n ) / 2 (m+n)/2 (m+n)/2 的元素; 当 m + n m+n m+n 为偶数时,中位数是两个有序数组中的下标为 ( m + n ) / 2 − 1 (m+n)/2-1 (m+n)/21 的元素和下标为 ( m + n ) / 2 (m+n)/2 (m+n)/2 的元素的平均值。
  2. 维护两个指针,初始时分别指向两个数组的下标 0 0 0 的位置,每次将指向较小值的指针后移一位(如果一个指针已经到达数组末尾,则只需要移动另一个数组的指针),直到到达中位数的位置。
  3. 如果一个数组已经遍历完,仍没有找到中位数,则直接计算跳跃的步数来找中位数。

在这里插入图片描述


代码

class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length; int n = nums2.length; int len = m + n; int left = 0; int right = 0; //合并之后的下标 int index = -1; int i = 0, j = 0; while(i < m && j < n){
index++; left = right; if(nums1[i] < nums2[j]){
right = nums1[i++]; }else {
right = nums2[j++]; } // 找到中位数,直接返回结果 if(index == len / 2){
return len % 2 == 0 ? (left + right) / 2.0 : right; } } // 遍历完其中一个数组,还没找到中位数,直接计算下标差 // 由于 下标 都进行了 加1 操作,所以计算步数时,需要减1 int steps = len / 2 - index - 1; // 需要找到 len / 2 - 1 if(i
0 ? nums1[i + steps - 1] : right; right = nums1[i + steps]; } else {
left = steps > 0 ? nums2[j + steps - 1]: right; right = nums2[j + steps]; } return len % 2 == 0 ? (left + right) / 2.0 : right; }}

复杂度分析

时间复杂度 O ( m + n ) O(m+n) O(m+n),其中其中 m m m n n n 分别是数组 n u m s 1 nums1 nums1 n u m s 2 nums2 nums2的长度。

空间复杂度 O ( 1 ) O(1) O(1)


方法二:二分法

解题思路

  1. 根据中位数的定义,当 m + n m+n m+n奇数时,中位数是两个有序数组中的 ( m + n ) / 2 + 1 (m+n)/2+1 (m+n)/2+1 个元素,当 m + n m+n m+n偶数时,中位数是两个有序数组中的 ( m + n ) / 2 (m+n)/2 (m+n)/2 个元素 ( m + n ) / 2 + 1 (m+n)/2+1 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k k k 小的数,其中 k k k ( m + n ) / 2 (m+n)/2 (m+n)/2 ( m + n ) / 2 + 1 (m+n)/2+1 (m+n)/2+1
  2. 假设两个有序数组分别是 n u m s 1 nums1 nums1 n u m s 2 nums2 nums2 。要找到第 k k k 个元素,我们可以比较 n u m s 1 [ k / 2 − 1 ] nums1[k/2-1] nums1[k/21] n u m s 2 [ k / 2 − 1 ] nums2[k/2-1] nums2[k/21]
        在 n u m s 1 [ k / 2 − 1 ] nums1[k/2-1] nums1[k/21] 前面的元素个数有 k / 2 − 1 k/2-1 k/21个,在 n u m s 2 [ k / 2 − 1 ] nums2[k/2-1] nums2[k/21]前面的元素个数有 k / 2 − 1 k/2-1 k/21个,对于 n u m s 1 [ k / 2 − 1 ] nums1[k/2-1] nums1[k/21] n u m s 2 [ k / 2 − 1 ] nums2[k/2-1] nums2[k/21]中较小者,最多有 k / 2 − 1 + k / 2 − 1 = k − 2 k/2-1 + k/2-1 = k - 2 k/21+k/21=k2个元素比它小,即不可能是 第 k k k 小的数。
    在这里插入图片描述
  3. 有以下三种情况需要特殊处理:
  • 如果下标为 k / 2 − 1 k/2−1 k/21 越界,那么选取对应数组中的最后一个元素,同时根据排除数的个数减少 k k k 的值。
  • 如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第 k k k 小的元素。
  • 如果 k = 1 k=1 k=1 ,我们只要返回两个数组首元素的最小值即可。

示例

    假设两个有序数组如下:

s u m s 1 sums1 sums1:   1   3   4   9

s u m s 2 sums2 sums2:   1   2   3   4   5   6   7   8   9

    两个有序数组的长度分别是 m = 4 m=4 m=4 n = 9 n=9 n=9,长度之和是 m + n = 13 m+n=13 m+n=13,中位数是两个有序数组中的第 k = ( m + n ) / 2 + 1 = 7 k = (m+n)/2+1 = 7 k=(m+n)/2+1=7 个元素,因此需要找到第 7 个元素。

  1.     比较两个有序数组中下标为 k / 2 − 1 = 2 k/2-1=2 k/21=2 的数,即 s u m s 1 [ 2 ] sums1[2] sums1[2] s u m s 2 [ 2 ] sums2[2] sums2[2]

在这里插入图片描述

            由于 s u m s 1 [ 2 ] > s u m s 2 [ 2 ] sums1[2] >sums2[2] sums1[2]>sums2[2],因此排除 s u m s 2 [ 0 ] sums2[0] sums2[0] s u m s 2 [ 1 ] sums2[1] sums2[1] s u m s 2 [ 2 ] sums2[2] sums2[2],即数组 s u m s 2 sums2 sums2的下标偏移(offset)变为 3 3 3,数组 s u m s 1 sums1 sums1的下标偏移(offset)仍为 0 0 0

            排除数的个数: k / 2 = 3 k/2=3 k/2=3; 更新 k k k 的值: k = k − k / 2 = 7 − 3 = 4 k=k-k/2=7-3=4 k=kk/2=73=4

  1.     比较两个有序数组中下标为 o f f s e t + k / 2 − 1 offset+k/2-1 offset+k/21 的数,即 s u m s 1 [ 1 ] sums1[1] sums1[1] s u m s 2 [ 4 ] sums2[4] sums2[4]
    在这里插入图片描述
       由于 s u m s 1 [ 1 ] < s u m s 2 [ 4 ] sums1[1] < sums2[4] sums1[1]<sums2[4],因此排除 s u m s 1 [ 0 ] sums1[0] sums1[0] s u m s 1 [ 1 ] sums1[1] sums1[1],即数组 s u m s 1 sums1 sums1的下标偏移(offset)变为 2 2 2,数组 s u m s 2 sums2 sums2的下标偏移(offset)仍为 3 3 3

            排除数的个数: 3 + k / 2 = 5 3+k/2=5 3+k/2=5; 更新 k k k 的值: k = k − k / 2 = 4 − 2 = 2 k=k-k/2=4-2=2 k=kk/2=42=2

  1.     比较两个有序数组中下标为 k / 2 − 1 = 0 k/2-1=0 k/21=0 的数,即 s u m s 1 [ 2 ] sums1[2] sums1[2] s u m s 2 [ 2 ] sums2[2] sums2[2]
    在这里插入图片描述
       由于 s u m s 1 [ 2 ] = s u m s 2 [ 3 ] sums1[2] = sums2[3] sums1[2]=sums2[3],因此排除 s u m s 1 [ 2 ] sums1[2] sums1[2],即数组 s u m s 1 sums1 sums1的下标偏移(offset)变为 3 3 3,数组 s u m s 2 sums2 sums2的下标偏移(offset)仍为 3 3 3

            排除数的个数: 5 + k / 2 = 6 5+k/2=6 5+k/2=6; 更新 k k k 的值: k = k − k / 2 = 2 − 1 = 1 k=k-k/2=2-1=1 k=kk/2=21=1

  1.     由于 k k k 的值变成 1 1 1,因此比较两个有序数组中的未排除下标范围内的第一个数,其中较小的数即为第 k k k 个数:
    在这里插入图片描述
       由于 s u m s 1 [ 3 ] > s u m s 2 [ 3 ] sums1[3] > sums2[3] sums1[3]>sums2[3],因此第 k k k 个数是 s u m s 2 [ 3 ] sums2[3] sums2[3]

代码

public double findMedianSortedArrays_2(int[] nums1, int[] nums2) {
/** * 当 m+n 是 奇数 时,中位数是两个有序数组中的第 (m+n)/2+1 个元素 * 当 m+n 是 偶数 时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值 */ int len = nums1.length + nums2.length; double median; if (len % 2 == 1) {
median = getKthElement(nums1, nums2, len / 2 + 1); } else {
median = (getKthElement(nums1, nums2, len / 2) + getKthElement(nums1, nums2, len / 2 + 1)) / 2.0; } return median; } /** * 找到第 k (k>1) 小的元素 * 循环取下标为 k/2-1 的两个元素进行比较,即 sums1[k/2-1]和 sums2[k/2-1] * 如果 sums1[k/2-1] <= sums2[k/2-1]) , 则 sums1 的 offset 变成 k/2, 即 sums1[0] 到 sums1[k/2-1] 都被剔除掉; * 如果 sums1[k/2-1] > sums2[k/2-1]) , 则 sums2 的 offset 变成 k/2, 即 sums2[0] 到 sums2[k/2-1] 都被剔除掉。 * 更新 k = k - k/2。 * 如果 下标 k/2-1 越界, 则 取 数组中最后一个, 更新 k 值 时,减去实际减少的值; * 如果 k = 1, 返回较小值, 即为 第 k 小的元素。 * */ public int getKthElement(int[] nums1, int[] nums2, int k) {
int length1 = nums1.length; int length2 = nums2.length; int index1 = 0, index2 = 0; while (true) {
// 边界情况 if (index1 == length1) {
return nums2[index2 + k - 1]; } if (index2 == length2) {
return nums1[index1 + k - 1]; } if (k == 1) {
return Math.min(nums1[index1], nums2[index2]); } // 正常情况 int half = k / 2; int newIndex1 = Math.min(index1 + half, length1) - 1; int newIndex2 = Math.min(index2 + half, length2) - 1; if (nums1[newIndex1] <= nums2[newIndex2]) {
k = k - (newIndex1 - index1 + 1); index1 = newIndex1 + 1; } else {
k = k - (newIndex2 - index2 + 1); index2 = newIndex2 + 1; } } }

复杂度分析

时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n)),其中 m m m n n n 分别是数组 n u m s 1 nums1 nums1 n u m s 2 nums2 nums2 的长度。

空间复杂度: O ( 1 ) O(1) O(1)


方法三:划分数组【官方答案】

解题思路

  1. 首先,在任意位置 i i i s u m s 1 sums1 sums1 划分成两个部分:

              left_sums1                              |                     right_sums1

sums1[0], sums1[1], … , sums1[i-1]     |     sums1[i], sums1[i+1], … , sums1[m-1]

       采用同样的方式,在任意位置 j j j s u m s 2 sums2 sums2 划分成两个部分:

              left_sums2                              |                     right_sums2

sums2[0], sums2[1], … , sums2[j-1]     |     sums2[j], sums2[j+1], … , sums2[n-1]

       将 left_sums1 和 left_sums2$ 放入一个集合,并将 right_sums1 和 right_sums2 放入另一个集合。 再把这两个新的集合分别命名为 left_part 和 right_part:

                left_part                                |                     right_part

sums1[0], sums1[1], … , sums1[i-1]     |     sums1[i], sums1[i+1], … , sums1[m-1]
sums2[0], sums2[1], … , sums2[j-1]     |     sums2[j], sums2[j+1], … , sums2[n-1]

  1. s u m s 1 sums1 sums1 s u m s 2 sums2 sums2的总长度是偶数时,如果我们能够保证:
  • l e n ( l e f t _ p a r t ) = l e n ( r i g h t _ p a r t ) \displaystyle len(left\_part) = len(right\_part) len(left_part)=len(right_part)
  • m a x ( l e f t _ p a r t ) ≤ m i n ( r i g h t _ p a r t ) \displaystyle max(left\_part) \leq min(right\_part) max(left_part)min(right_part)

       那么中位数就是前一部分的最大值和后一部分的最小值的平均值:

m e d i a n = m a x ( l e f t _ p a r t ) + m i n ( r i g h t _ p a r t ) 2 \displaystyle median = \frac{max(left\_part)+min(right\_part)}{2} median=2max(left_part)+min(right_part)

       当 s u m s 1 sums1 sums1 s u m s 2 sums2 sums2的总长度是奇数时,如果我们能够保证:

  • l e n ( l e f t _ p a r t ) = l e n ( r i g h t _ p a r t ) + 1 \displaystyle len(left\_part) = len(right\_part) + 1 len(left_part)=len(right_part)+1
  • m a x ( l e f t _ p a r t ) ≤ m i n ( r i g h t _ p a r t ) \displaystyle max(left\_part) \leq min(right\_part) max(left_part)min(right_part)

       那么中位数就是前一部分的最大值和后一部分的最小值的平均值:

m e d i a n = m a x ( l e f t _ p a r t ) \displaystyle median = max(left\_part) median=max(left_part)

       对于第一个条件,总长度是偶数和奇数的情况,将两种情况合并。

  • 当 m+n 为偶数: i + j = m − i + n − j = m + n 2 = m + n + 1 2 \displaystyle i+j=m−i+n−j = \frac{m+n}{2} = \frac{m+n+1}{2} i+j=mi+nj=2m+n=2m+n+1
  • 当 m+n 为奇数: i + j = m − i + n − j + 1 = m + n + 1 2 \displaystyle i+j=m−i+n−j+1 = \frac{m+n+1}{2} i+j=mi+nj+1=2m+n+1
    即:
              j = m + n + 1 2 − i \displaystyle j = \frac{m+n+1}{2} - i j=2m+n+1i
    注:规定 m ≤ n ( 如 果 不 满 足 , 就 交 换 s u m s 1 和 s u m s 2 ) \displaystyle m \leq n(如果不满足 , 就交换 sums1 和 sums2 ) mn,sums1sums2。这样对于任意的 i ∈ [ 0 , m ] \displaystyle i \in [0, m] i[0,m],都有 j = m + n + 1 2 − i ∈ [ 0 , n ] \displaystyle j = \frac{m + n + 1}{2} - i \in [0, n] j=2m+n+1i[0,n]

       第二个条件对于总长度是偶数和奇数的情况是一样的。

  • m a x ( l e f t _ p a r t ) ≤ m i n ( r i g h t _ p a r t ) \displaystyle max(left\_part) \leq min(right\_part) max(left_part)min(right_part)
    即 :
              s u m s 1 [ i − 1 ] ≤ s u m s 2 [ j ] \displaystyle sums1[i-1] \leq sums2[j] sums1[i1]sums2[j]
              s u m s 2 [ j − 1 ] ≤ s u m s 1 [ i ] \displaystyle sums2[j-1] \leq sums1[i] sums2[j1]sums1[i]
  1. 因此,在 [ 0 , m ] \displaystyle [0,m] [0,m] 中找到 i i i,使得:

s u m s 1 [ i − 1 ] ≤ s u m s 2 [ j ] \displaystyle sums1[i-1] \leq sums2[j] sums1[i1]sums2[j] s u m s 2 [ j − 1 ] ≤ s u m s 1 [ i ] \displaystyle sums2[j-1] \leq sums1[i] sums2[j1]sums1[i] ,其中 j = m + n + 1 2 − i \displaystyle j = \frac{m + n + 1}{2} - i j=2m+n+1i

       由于 i ∈ [ 0 , m ] \displaystyle i \in [0, m] i[0,m],随着 i i i 递增, s u m s 1 [ i − 1 ] \displaystyle sums1[i−1] sums1[i1] 递增, s u m s 2 [ j ] \displaystyle sums2[j] sums2[j] 递减,所以一定存在一个最大的 i i i 满足 s u m s 1 [ i − 1 ] ≤ s u m s 2 [ j ] \displaystyle sums1[i-1] \leq sums2[j] sums1[i1]sums2[j]

       i + 1 i+1 i+1 就不满足。将 i + 1 i+1 i+1 带入,可以得到 s u m s 1 [ i ] > s u m s 2 [ j − 1 ] \displaystyle sums1[i]> sums2[j-1] sums1[i]>sums2[j1]

       因此,此问题转换为,由于 i ∈ [ 0 , m ] \displaystyle i \in [0, m] i[0,m] 的区间上进行二分搜索,找到最大的 i i i 值满足 s u m s 1 [ i − 1 ] ≤ s u m s 2 [ j ] , 其 中 j = m + n + 1 2 − i \displaystyle sums1[i-1] \leq sums2[j],其中 j = \frac{m + n + 1}{2} - i sums1[i1]sums2[j]j=2m+n+1i


代码

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 确保 m <= n if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1); } int m = nums1.length; int n = nums2.length; int left = 0, right = m; // median1:前一部分的最大值 // median2:后一部分的最小值 int median1 = 0, median2 = 0; while (left <= right) {
// 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1] // 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1] int i = (left + right) / 2; int j = (m + n + 1) / 2 - i; // nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j] int nums_im1 = (i == 0 ? Integer.MIN_VALUE : nums1[i - 1]); int nums_i = (i == m ? Integer.MAX_VALUE : nums1[i]); int nums_jm1 = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]); int nums_j = (j == n ? Integer.MAX_VALUE : nums2[j]); if (nums_im1 <= nums_j) {
median1 = Math.max(nums_im1, nums_jm1); median2 = Math.min(nums_i, nums_j); left = i + 1; } else {
right = i - 1; } } return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1; }

复杂度分析

  • 时间复杂度: O ( log ⁡ min ⁡ ( m , n ) ) ) \displaystyle O(\log\min(m,n))) O(logmin(m,n))),其中 m m m n n n 分别是数组 $nums1$ 和 n u m s 2 nums2 nums2的长度。
  • 空间复杂度:O(1)。

转载地址:http://wgdlf.baihongyu.com/

你可能感兴趣的文章
Openshift 4.4 静态 IP 离线安装系列(一):准备离线资源
查看>>
万字长文,说透了 Openshift4 的安装过程!
查看>>
Envoy 中文指南系列:Envoy 介绍
查看>>
[译] BeyondProd:云原生安全的一种新方法(Google, 2019)
查看>>
什么?VMware Fusion 也能 docker run 了?
查看>>
教你玩转微服务的装逼指南!
查看>>
Envoy 中文指南系列:Sidecar 模式
查看>>
面试官邪魅一笑:你猜一个 TCP 重置报文的序列号是多少?
查看>>
Envoy 中文指南系列: 安装
查看>>
最华丽的 Kubernetes 桌面客户端:Lens
查看>>
太赞了,这个神器竟然能分分钟将多个 kubeconfig 合并成一个!
查看>>
如何解决容器中 nginx worker process 自动设置的问题
查看>>
ethtool 原理介绍和解决网卡丢包排查思路
查看>>
HPE 推出容器平台 Ezmeral,向 VMware 与 Red Hat 下战书
查看>>
使用 Prometheus-Operator 监控 Calico
查看>>
如果你不习惯新版的 Github 的 UI 界面,可以试试这款插件
查看>>
容器化囧途——没上容器时好好的?
查看>>
linux内核网络参数tcp_tw_recycle 和 tcp_tw_reuse 你搞清楚了吗?
查看>>
40核CPU+80G内存的云资源终终终终终于免费了!
查看>>
Drone开源持续集成工具——Pipeline篇
查看>>