LeetCode - 解题笔记 - 4 - Median of Two Sorted Arrays

Median of Two Sorted Arrays

Solution 1

(这个题的思路稍稍有点复杂,来自官方的解答)

单纯从时间要求和数据量上看,这道题只能是使用对数复杂度的算法(二分、堆……)。从问题已知条件上看,首先中位数一定存在,其次两个数组有序。

中位数的定义

本题用到的一个很关键的思路就是直接在有序数组上反映中位数的数学定义:给定的 n n n个数中,存在1个数(奇数个)或者2个数(偶数个)的平均数,会有 n 1 n_1 n1个数比它小, n 2 n_2 n2个数比它大, n 1 = n 2 n_1 = n_2 n1=n2。那么对于两个数组来说,其中的中位数,可以直接反应为:两个数组的长度分别为 m m m n n n,那么存在1个数(省略上面复杂的叙述),有两组数量相等数的集合,其中一个里面所有数比它大,另外一个都比它小。那么可以分别找到存在于两个数组的数字,比二者都小的和比二者都大的数目相当,然后根据二者大小选择合适的一个就好了。

问题建模

(下面沿用官方解答的符号表示)

那么第一个数组存在一个索引 i i i,第二个数组存在一个索引 j j j,二者存在如下关系:

  1. i + j = ( m + n ) − ( i + j ) i + j = (m + n) - (i + j) i+j=(m+n)(i+j)(偶数情形,求和平均)或者 i + j = ( m + n ) − ( i + j ) + 1 i + j = (m + n) - (i + j) + 1 i+j=(m+n)(i+j)+1(奇数情形,多算了一个)
  2. 保证 B [ j − 1 ] ≤ A [ i ] B[j−1] \le A[i] B[j1]A[i] A [ i − 1 ] ≤ B [ j ] A[i−1] \le B[j] A[i1]B[j],对应着上面对中位数的定义

为了统一第一个定义中的表示,官方的解答中统一使用了+1的计算方法,这在实际中就 i i i j j j就对应着实际中位数的右侧一个,因此真的考察中位数的时候,需要-1处理。这也就导致了这个索引的取值范围为 [ 0 , m ] [0, m] [0,m],其中取0的含义为中位数必然位于另外一个数组。这里为了统一表示,假设 m ≤ n m \le n mn(为了代码简便,否则界限判定的代码需要重复一遍),那么两个索引的取值关系有:

  1. i ∈ [ 0 , m ] i \in [0, m] i[0,m]
  2. j = ⌊ m + n + 1 2 ⌋ − i j = \lfloor\frac{m + n + 1}{2} \rfloor - i j=2m+n+1i

那么这个时候,如果我们找到了满足要求的索引,那么中位数的表示为:

  1. 如果总共奇数个数字,那么需要找到两个索引前一个位置稍大的一个,即 max ⁡ ( A [ i − 1 ] , B [ j − 1 ] ) \max(A[i - 1], B[j - 1]) max(A[i1],B[j1])
  2. 如果总共偶数个数字,那么除了找到两个索引前一个位置稍大的一个(左侧值),还要找到当前索引的稍小的一个(右侧值)计算平均值,即 1 2 { max ⁡ ( A [ i − 1 ] , B [ j − 1 ] ) + min ⁡ ( A [ i ] , B [ j ] ) } \frac 12 \{\max(A[i - 1], B[j - 1]) + \min(A[i], B[j])\} 21{max(A[i1],B[j1])+min(A[i],B[j])}

边界值的处理

官方认为有效取之覆盖到0到长度,左边界可以视为中位数必在另一个数组中,因此可以将对应的(-1项会变成负的)一项直接从判定条件中去掉。对于右边界实际上不会影响取值,但是在总数为偶数个的情形下,需要判断右侧值,因此此时右边界的意义在于中位数必然在另外一个数组中,因此将判定条件相应去掉。

还有一个特殊的性质是,如果其中一个索引不在边界,那么另外一个索引同样不在边界(可以证明,省略),因此可以相应地简化判定过程。

搜索策略

由于已知数组有序,因此涉及到大小的问题,直接使用前后两半代称。此外已经假设 m ≤ n m \le n mn,因此后面只考虑调整第一个数组的索引的搜索策略。

在不考虑边界值的情况下,选择的两个准备计算中位数的索引位置需要满足的条件有:

1. 第一个数组的索引位置的数字必须要比第二个数组索引位置的前半部分更大

2. 第二个数组的索引位置的数字必须要比第一个数组索引位置的前半部分更大

因此,搜索过程中涉及到对应的两个违例情形(上述两种情况显然不会同时出现):

  1. 此时需要一个在第一个数组中找到一个更靠后的位置,而对应的第二个数组的前半部分会“更小”,因此搜索方向是向后搜索
  2. 类似地,这种情况下需要向前搜索

考虑到数组本身有序,且数据量不允许完全遍历,此时需要使用二分查找寻找理想的索引位置。

  • 时间复杂度: O ( log ⁡ ( max ⁡ ( m , n ) ) ) O(\log(\max(m, n))) O(log(max(m,n))) m m m n n n为两个数组的长度
  • 空间复杂度: O ( 1 ) O(1) O(1),仅需要一些搜索过程中的状态维护变量
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        // 为了方便处理,保证1的长度小于2
        if (nums1.size() > nums2.size()) {
            auto temp = nums1;
            nums1 = nums2;
            nums2 = temp;
        }
        int iMin = 0, iMax = nums1.size();
        int lenHalf = (nums1.size() + nums2.size() + 1) / 2;
        while (iMin <= iMax) {
            int i = (iMin + iMax ) / 2;
            int j = lenHalf - i;
            if (i < iMax && nums1[i] < nums2[j - 1]) {
                iMin = i + 1;
            }
            else if(i > iMin && nums1[i - 1] > nums2[j]) {
                iMax = i - 1;
            }
            else {
                double mid = 0, mid1 = 0; // 因为计算的时候+1,因此中位数在左侧-1
                // 边界条件
                if (i == 0) {
                    mid1 = nums2[j - 1]; // i为0,此时m一定有效
                }
                else if (j == 0) {
                    mid1 = nums1[i - 1];
                }
                else {
                    mid1 = max(nums1[i - 1], nums2[j - 1]);
                }
                
                if ((nums1.size() + nums2.size()) % 2 == 0) {
                    int mid2 = 0;
                    if (i == nums1.size()) {
                        mid2 = nums2[j]; // i为0,此时m一定有效
                    }
                    else if (j == nums2.size()) {
                        mid2 = nums1[i];
                    }
                    else {
                        mid2 = min(nums1[i], nums2[j]);
                    }
                    mid = (mid1 + mid2) / 2;
                    // cout << mid << " " << mid1 << " " << mid2 << endl;
                }
                else {
                    mid = mid1;
                    // cout << mid << " " << mid1 << endl;
                }
                return mid;
            }
        }
        return -1;
    }
};

solution 2

Solution 1的Python实现

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        if len(nums1) > len(nums2):
            nums1, nums2 = nums2, nums1
            
        i_min, i_max = 0, len(nums1)
        len_half = (len(nums1) + len(nums2) + 1) // 2
        
        while i_min <= i_max:
            i = (i_min + i_max) // 2
            j = len_half - i
            if i < len(nums1) and nums1[i] < nums2[j - 1]:
                i_min = i + 1
            elif i > 0 and nums1[i - 1] > nums2[j]:
                i_max = i - 1
            else:
                if i == 0:
                    mid1 = nums2[j - 1]
                elif j == 0:
                    mid1 = nums1[i - 1]
                else:
                    mid1 = max(nums1[i - 1], nums2[j - 1])
                    
                if (len(nums1) + len(nums2)) % 2 == 0:
                    if i == len(nums1):
                        mid2 = nums2[j]
                    elif j == len(nums2):
                        mid2 = nums1[i]
                    else:
                        mid2 = min(nums1[i], nums2[j])
                        
                    mid = (mid1 + mid2) / 2
                else:
                    mid = mid1;
                    
                return mid;
        return -1.0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值