程序员算法题每天一练0004. 寻找两个正序数组的中位数[困难](多种语言实现)

题目描述

给定两个大小分别为 m 和 n 的正序(从小到大)数组 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

为了在时间复杂度为 O(log(m+n)) 的情况下找到两个正序数组的中位数,可以使用一种叫作"中位数的性质"(Median of Medians)的算法。这个算法的基本思想是通过不断缩小搜索范围来找到中位数。下面代码实现了一个时间复杂度为 O(log(min(m, n))) 的算法,通过二分查找的方式在较短的数组上执行,以达到 O(log(m+n)) 的整体时间复杂度。

递归函数的算法原理是基于分治策略的。它用于在两个已排序数组 nums1 和 nums2 中寻找第 k 个元素。

算法步骤如下:

  1. 首先,函数会检查两个数组的长度,如果其中一个数组的长度小于等于 k,那么函数就会直接返回另一个数组的第 k 个元素。
  2. 如果两个数组的长度都大于 k,那么函数会取 k 的一半,即 p = k / 2。这里用到了二分查找的思想,即将问题规模缩小一半。
  3. 然后,函数会在两个数组中寻找第 p 个元素,记为 x 和 y
  4. 如果 x 小于 y,那么说明第 k 个元素一定在 nums1 的后半段,因此在 nums1 的后半段和 nums2 中寻找第 k - p 个元素。
  5. 如果 x 大于等于 y,那么说明第 k 个元素一定在 nums2 的后半段,因此在 nums1 的前半段和 nums2 的后半段中寻找第 k - p 个元素。
  6. 重复以上步骤,直到找到第 k 个元素,或者剩余的元素个数小于等于 p

这个函数的作用是在两个已排序数组中寻找第 k 个元素,它利用了分治策略和二分查找的思想,可以高效地处理大规模数据。

这个代码实现了一个寻找两个有序数组的中位数的算法。为了方便解释,我们先定义以下的基本概念:

  • nums1 和 nums2 是两个有序数组,它们的长度分别为 m 和 n
  • 中位数:对于一个有序数组,中位数是指位于数组中间位置的数。当数组的长度为奇数时,中位数就是位于正中间的数;当数组的长度为偶数时,中位数是位于中间两个数的平均值。

该算法的核心思路是利用二分法进行查找。具体来说,这个函数通过比较两个数组的相应元素来决定下一步查找的方向。函数 f(i, j, k) 用来查找由 nums1 的前 i 个元素和 nums2 的前 j 个元素组成的长度为 k 的子序列的中位数。这里的 ij 和 k 都是整数。

函数的返回值是这个子序列的中位数。其中,p 是 k 的一半,x 和 y 分别是 nums1 和 nums2 在对应位置上的元素(如果该位置越界,则认为对应的元素为无穷大)。然后根据 x 和 y 的大小关系来递归查找子序列的中位数。

最后,该算法分别以 (m + n + 1) // 2 和 (m + n + 2) // 2 为长度调用函数 f 来找到两个中位数,然后取这两个中位数的平均值作为最终结果。这样做是为了处理 m + n 为奇数和偶数两种情况。

这是一个具体的例子来演示这个函数是如何工作的:

假设我们有两个有序数组 nums1 = [1, 3, 5] 和 nums2 = [2, 4, 6]。首先,我们会把这两个数组合并成一个有序数组 [1, 2, 3, 4, 5, 6],然后取中间的两个数 3 和 4 的平均值,即 3.5,作为两个有序数组的中位数。

C++

#include <functional>
#include <vector>
#include <iostream>
using namespace std;
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size();
        // 定义一个递归函数f,用于查找两个数组中第k小的数
        function<int(int, int, int)> f = [&](int i, int j, int k) {
            if (i >= m) {
                return nums2[j + k - 1]; // nums1中已经没有元素,直接返回nums2中第k小的数
            }
            if (j >= n) {
                return nums1[i + k - 1]; // nums2中已经没有元素,直接返回nums1中第k小的数
            }
            if (k == 1) {
                return min(nums1[i], nums2[j]); // 如果k为1,直接返回两个数组当前位置的较小值
            }
            int p = k / 2; // 将k分为两部分
            // 分别取两个数组中第i+p-1和j+p-1位置的数,若越界则取一个较大的数
            int x = i + p - 1 < m ? nums1[i + p - 1] : 1 << 30;
            int y = j + p - 1 < n ? nums2[j + p - 1] : 1 << 30;
            // 根据x和y的大小关系递归调用f函数
            return x < y ? f(i + p, j, k - p) : f(i, j + p, k - p);
            };
        // 调用f函数分别找到第(m+n+1)/2和(m+n+2)/2小的数
        int a = f(0, 0, (m + n + 1) / 2);
        int b = f(0, 0, (m + n + 2) / 2);
        return (a + b) / 2.0; // 返回中位数
    }
};

int main()
{
    vector<int> nums1 = { 1, 2 };
    vector<int> nums2 = { 3, 4 };
    Solution sol;
    double median = sol.findMedianSortedArrays(nums1, nums2);
    cout << "Median: " << median << endl;
}

C++ 另一个表达

#include <vector>
#include <iostream>
#include <climits> // 包含用于INT_MAX的定义
using namespace std;
class Solution {

private:
        // 在两个已排序数组中寻找第k个元素的函数
        int findKthElement(std::vector<int>& nums1, std::vector<int>& nums2, int i, int j, int k) {
            int m = nums1.size(), n = nums2.size();
            if (i >= m) {
                // 如果数组1已经遍历完成,则直接返回数组2中的第k个元素
                return nums2[j + k - 1];
            }
            if (j >= n) {
                // 如果数组2已经遍历完成,则直接返回数组1中的第k个元素
                return nums1[i + k - 1];
            }
            if (k == 1) {
                // 当k=1时,返回数组1和数组2当前位置上的较小值
                return std::min(nums1[i], nums2[j]);
            }
            // 取k的一半,分别在nums1和nums2数组中寻找第k/2个元素
            int p = k / 2;
            int x = i + p - 1 < m ? nums1[i + p - 1] : INT_MAX;
            int y = j + p - 1 < n ? nums2[j + p - 1] : INT_MAX;
            if (x < y) {
                // 如果nums1中对应位置的值较小,则递归在剩余部分中寻找第k-p个元素
                return findKthElement(nums1, nums2, i + p, j, k - p);
            }
            else {
                // 如果nums2中对应位置的值较小,则递归在剩余部分中寻找第k-p个元素
                return findKthElement(nums1, nums2, i, j + p, k - p);
            }
        }

public:
        // 计算两个已排序数组的中位数的函数
        double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
            int m = nums1.size(), n = nums2.size();
            int a = findKthElement(nums1, nums2, 0, 0, (m + n + 1) / 2);
            int b = findKthElement(nums1, nums2, 0, 0, (m + n + 2) / 2);
            return (a + b) / 2.0;
        }
    
};

int main()
{
    vector<int> nums1 = { 1, 2 };
    vector<int> nums2 = { 3, 4 };
    Solution sol;
    double median = sol.findMedianSortedArrays(nums1, nums2);
    cout << "Median: " << median << endl;
}

C#

using System;

public class Solution
{
    // 在两个已排序数组中寻找第k个元素的函数
    public int FindKthElement(int[] nums1, int[] nums2, int i, int j, int k)
    {
        int m = nums1.Length, n = nums2.Length;
        if (i >= m)
        {
            // 如果数组1已经遍历完成,则直接返回数组2中的第k个元素
            return nums2[j + k - 1];
        }
        if (j >= n)
        {
            // 如果数组2已经遍历完成,则直接返回数组1中的第k个元素
            return nums1[i + k - 1];
        }
        if (k == 1)
        {
            // 当k=1时,返回数组1和数组2当前位置上的较小值
            return Math.Min(nums1[i], nums2[j]);
        }
        // 取k的一半,分别在nums1和nums2数组中寻找第k/2个元素
        int p = k / 2;
        int x = i + p - 1 < m ? nums1[i + p - 1] : int.MaxValue;
        int y = j + p - 1 < n ? nums2[j + p - 1] : int.MaxValue;
        if (x < y)
        {
            // 如果nums1中对应位置的值较小,则递归在剩余部分中寻找第k-p个元素
            return FindKthElement(nums1, nums2, i + p, j, k - p);
        }
        else
        {
            // 如果nums2中对应位置的值较小,则递归在剩余部分中寻找第k-p个元素
            return FindKthElement(nums1, nums2, i, j + p, k - p);
        }
    }

    // 计算两个已排序数组的中位数的函数
    public double FindMedian(int[] nums1, int[] nums2)
    {
        int m = nums1.Length, n = nums2.Length;
        int a = FindKthElement(nums1, nums2, 0, 0, (m + n + 1) / 2);
        int b = FindKthElement(nums1, nums2, 0, 0, (m + n + 2) / 2);
        return (a + b) / 2.0;
    }

    public static void Main()
    {
        Solution solution = new Solution();

        int[] nums1 = { 1, 3, 5 };
        int[] nums2 = { 2, 4, 6 };

        double median = solution.FindMedian(nums1, nums2);

        Console.WriteLine("The median of the two sorted arrays is: " + median);
    }

}

Python

from typing import List

# 定义一个名为Solution的类  

class Solution:  
    # 在Solution类中定义一个方法findMedianSortedArrays,它接受两个列表nums1和nums2作为输入参数,返回一个浮点数  
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:  
        # 定义一个内部函数f,它接受三个整数参数i, j, k,返回一个整数  
        def f(i: int, j: int, k: int) -> int:  
            # 如果i大于等于m(表示nums1的索引已经越界),则返回nums2在对应位置的元素  
            if i >= m:  
                return nums2[j + k - 1]  
            # 如果j大于等于n(表示nums2的索引已经越界),则返回nums1在对应位置的元素  
            if j >= n:  
                return nums1[i + k - 1]  
            # 如果k等于1(表示我们只需要再查看一个元素就能确定结果),则返回nums1和nums2在对应位置的较小值  
            if k == 1:  
                return min(nums1[i], nums2[j])  
            # k除以2得到p,即我们需要查看的元素的半数  
            p = k // 2  
            # 获取nums1在位置i+p-1的元素,如果索引越界,则认为它是无穷大(inf)  
            x = nums1[i + p - 1] if i + p - 1 < m else inf  
            # 获取nums2在位置j+p-1的元素,如果索引越界,则认为它是无穷大(inf)  
            y = nums2[j + p - 1] if j + p - 1 < n else inf  
            # 如果x小于y,则在i+p和j之间递归调用f函数并返回结果,否则在i和j+p之间递归调用f函数并返回结果  
            return f(i + p, j, k - p) if x < y else f(i, j + p, k - p)  
  
        # 获取nums1和nums2的长度,分别赋值给m和n  
        m, n = len(nums1), len(nums2)  
        # 通过f函数查找长度为(m+n+1)//2的子序列的中位数,赋值给a  
        a = f(0, 0, (m + n + 1) // 2)  
        # 通过f函数查找长度为(m+n+2)//2的子序列的中位数,赋值给b  
        b = f(0, 0, (m + n + 2) // 2)  
        # 返回a和b的平均值,即为两个有序数组的中位数  
        return (a + b) / 2
    

# 定义两个有序数组  
nums1 = [1, 3, 5]  
nums2 = [2, 4, 6]  
  
# 调用函数并打印结果  
result = Solution().findMedianSortedArrays(nums1, nums2)  
print(result)  # 输出 3.5

Java

// 定义一个名为Solution的类  
class Solution {  
    // 定义私有整数变量m和n,分别用来存储两个输入数组的长度  
    private int m;  
    private int n;  
    // 定义私有整数数组nums1和nums2,用来存储输入的两个数组  
    private int[] nums1;  
    private int[] nums2;  
  
    // 定义公有方法findMedianSortedArrays,输入两个整数数组nums1和nums2,输出它们的中位数  
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {  
        // 将输入数组的长度分别赋值给m和n  
        m = nums1.length;  
        n = nums2.length;  
        // 将输入数组分别赋值给类的私有数组nums1和nums2  
        this.nums1 = nums1;  
        this.nums2 = nums2;  
        // 调用私有方法f,计算长度为(m+n+1)/2的子序列的中位数,存入变量a  
        int a = f(0, 0, (m + n + 1) / 2);  
        // 调用私有方法f,计算长度为(m+n+2)/2的子序列的中位数,存入变量b  
        int b = f(0, 0, (m + n + 2) / 2);  
        // 返回两个中位数的平均值,即原数组的中位数  
        return (a + b) / 2.0;  
    }  
  
    // 定义私有方法f,输入三个参数i、j、k,返回一个整数  
    private int f(int i, int j, int k) {  
        // 如果i大于等于m,说明在nums1中已经遍历完了,返回nums2在j和k位置的元素  
        if (i >= m) {  
            return nums2[j + k - 1];  
        }  
        // 如果j大于等于n,说明在nums2中已经遍历完了,返回nums1在i和k位置的元素  
        if (j >= n) {  
            return nums1[i + k - 1];  
        }  
        // 如果k等于1,说明只需要再查看一个元素就可以确定中位数,返回nums1在i位置的元素和nums2在j位置的元素的较小值  
        if (k == 1) {  
            return Math.min(nums1[i], nums2[j]);  
        }  
        // 将k除以2得到p,即需要查看的元素的半数  
        int p = k / 2;  
        // 如果i+p-1小于m,说明可以查看nums1在i+p-1位置的元素,否则该元素的值被视为一个很大的数(1 << 30)  
        int x = i + p - 1 < m ? nums1[i + p - 1] : 1 << 30;  
        // 如果j+p-1小于n,说明可以查看nums2在j+p-1位置的元素,否则该元素的值被视为一个很大的数(1 << 30)  
        int y = j + p - 1 < n ? nums2[j + p - 1] : 1 << 30;  
        // 如果x小于y,则在i+p和j之间递归调用f方法并返回结果,否则在i和j+p之间递归调用f方法并返回结果  
        return x < y ? f(i + p, j, k - p) : f(i, j + p, k - p);  
    }  
}

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值