leecode题解-4. 寻找两个正序数组的中位数

解题思路:

1. 为了确保算法的效率,将长度较短的数组作为 `nums1`,长度较长的数组作为 `nums2`,这样可以减小二分查找的范围。

if (nums1Size > nums2Size) {
        return findMedianSortedArrays(nums2, nums2Size, nums1, nums1Size);
    }

注:

这段代码确保了 `nums1` 的长度小于等于 `nums2` 的长度。

如果 `nums1` 的长度大于 `nums2` 的长度,就调用递归函数 `findMedianSortedArrays`,将 `nums2` 和 `nums1` 交换位置传入,以确保 `nums1` 的长度始终小于等于 `nums2` 的长度。

任何一个函数调用自身都可以被称为递归。

2. 确定两个数组的长度 `m` 和 `n`,同时定义二分查找的边界 `left` 和 `right`。

    int m = nums1Size;
    int n = nums2Size;

    // 定义二分查找的边界
    int left = 0;
    int right = m;
    int halfLen = (m + n + 1) / 2; // 用于确定分割点的位置

3. 在 `nums1` 中选择一个分割点 `partition1`,并通过 `partition1` 计算出 `nums2` 中的分割点 `partition2`。

int partition1 = left + (right - left) / 2; // 在 nums1 中选择一个分割点
int partition2 = halfLen - partition1; // 在 nums2 中根据分割点计算得到 nums2 的分割点

注:

使用 `(right - left) / 2` 来计算 `nums1` 的分割点 `partition1`

使用 `halfLen - partition1` 来计算 `partition2` 。

这种计算方式是为了确保分割点的位置与数组的左右边界 `left` 、 `right` 相关,并且可以在后续的迭代过程中进行调节。

以根据当前的分割点位置和左右边界进行动态的调节,以逐步接近正确的分割点,最终找到划分两个数组的合适位置。

分割点的选择要满足以下条件:
1)分割点左侧的元素都小于等于分割点右侧的元素。
2)`maxLeft1 <= minRight2` 和 `maxLeft2 <= minRight1`。
3)其中,`maxLeft1` 和 `maxLeft2` 分别表示左侧的最大值,`minRight1` 和 `minRight2` 分别表示右侧的最小值。

int maxLeft1 = (partition1 == 0) ? INT_MIN : nums1[partition1 - 1];
//`maxLeft1` 表示 `nums1` 中分割点左侧的最大值。
//如果 `partition1` 等于 0,表示分割点位于 `nums1` 的起始位置,此时将 `maxLeft1` 设置为 `INT_MIN`,表示不存在左侧的最大值。
//否则,将 `maxLeft1` 设置为 `nums1[partition1 - 1]`,即分割点左侧的元素。

int minRight1 = (partition1 == m) ? INT_MAX : nums1[partition1];
//`minRight1` 表示 `nums1` 中分割点右侧的最小值。
//如果 `partition1` 等于 `m`,表示分割点位于 `nums1` 的末尾位置,此时将 `minRight1` 设置为 `INT_MAX`,表示不存在右侧的最小值。
//否则,将 `minRight1` 设置为 `nums1[partition1]`,即分割点右侧的元素。

int maxLeft2 = (partition2 == 0) ? INT_MIN : nums2[partition2 - 1];
//`maxLeft2` 表示 `nums2` 中分割点左侧的最大值。
//如果 `partition2` 等于 0,表示分割点位于 `nums2` 的起始位置,此时将 `maxLeft2` 设置为 `INT_MIN`,表示不存在左侧的最大值。
//否则,将 `maxLeft2` 设置为 `nums2[partition2 - 1]`,即分割点左侧的元素。

int minRight2 = (partition2 == n) ? INT_MAX : nums2[partition2];
//`minRight2` 表示 `nums2` 中分割点右侧的最小值。
//如果 `partition2` 等于 `n`,表示分割点位于 `nums2` 的末尾位置,此时将 `minRight2` 设置为 `INT_MAX`,表示不存在右侧的最小值。
//否则,将 `minRight2` 设置为 `nums2[partition2]`,即分割点右侧的元素。

 注:

当分割点位于数组边界时,使用 `INT_MIN` 、`INT_MAX` 来表示不存在左侧的最大值或右侧的最小值。

这是为了确保在计算中位数时,对不存在的元素取最小或最大值,以保持算法的正确性。

使用 `INT_MIN` 是一种约定俗成的方式,用于表示这种特殊情况。

触碰到 INT_MIN 和 INT_MAX 的情况通常是在以下情况下发生:

1)整数溢出:当进行整数运算时,如果结果超出了整数类型的取值范围,就会发生溢出。例如,对一个正整数加上 INT_MAX,或对一个负整数减去 INT_MAX,可能导致溢出并得到一个超出 INT_MAX 或 INT_MIN 的结果。
2)边界检查:有时在编程中需要对变量或表达式的值进行边界检查,以确保不超出 INT_MIN 和 INT_MAX 的范围。例如,当进行数组索引操作时,需要确保索引值在合法范围内,否则可能导致越界访问。
3)强制类型转换:进行类型转换时,如果将一个超出 INT_MIN 和 INT_MAX 范围的值强制转换为整数类型,结果可能被截断为 INT_MIN 或 INT_MAX。

INT_MAX 与 MAX_VALUE:

在 C 语言中,INT_MAX 是表示有符号整数类型的最大正数值的常量,是 2147483647。

这对应于 32 位有符号整数类型(如 int)的最大正数值。

C 语言没有提供名为 MAX_VALUE 的常量。

在 C++ 中,INT_MAX 是表示有符号整数类型的最大正数值的常量;UINT_MAX 的常量用于表示无符号整数类型的最大值,是 4294967295,对应于 32 位无符号整数类型(如 unsigned int)的最大值。INT_MAX 、UINT_MAX 可通过包含 `<climits>` 头文件来访问。

4. 判断分割点是否合理,如果合理,则根据数组总长度的奇偶性计算中位数:
1)如果数组总长度为奇数,中位数为 `max(maxLeft1, maxLeft2)`

注:

当数组的总长度为奇数时,只需要关注左侧部分的最大值,并将其作为中位数返回。

这是因为在奇数长度的数组中左侧部分的元素个数比右侧部分多一个

当总长度为奇数时,分割点为:`(m + n) % 2 == 1`,所以左侧部分的元素个数会比右侧部分多一个。


2)如果数组总长度为偶数,中位数 \`(max(maxLeft1, maxLeft2) + min(minRight1, minRight2)) / 2

 注:

`fmax()` 和 `fmin()` 用于返回两个数中的较大值和较小值,适用于浮点数类型(`float`、`double` 和 `long double`)。

如果需要比较整数类型的数值,可以使用 `max()` 和 `min()` 函数。

这些函数在使用之前需要包含相应的头文件。对于浮点数比较,需要包含 `<math.h>` 头文件,对于整数比较,需要包含 `<algorithm>` 头文件。

    while (left <= right) {
        int partition1 = left + (right - left) / 2; // 在 nums1 中选择一个分割点
        int partition2 = halfLen - partition1; // 在 nums2 中根据分割点计算得到 nums2 的分割点

        // 获取分割点左侧的最大值和右侧的最小值
        int maxLeft1 = (partition1 == 0) ? INT_MIN : nums1[partition1 - 1];
        int minRight1 = (partition1 == m) ? INT_MAX : nums1[partition1];
        int maxLeft2 = (partition2 == 0) ? INT_MIN : nums2[partition2 - 1];
        int minRight2 = (partition2 == n) ? INT_MAX : nums2[partition2];

        if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
            // 分割点合理,根据数组总长度的奇偶性计算中位数
            if ((m + n) % 2 == 0) {
                return (double)(fmax(maxLeft1, maxLeft2) + fmin(minRight1, minRight2)) / 2.0;
            } else {
                return (double)fmax(maxLeft1, maxLeft2);
            }

 5. 如果分割点不合理,根据大小关系调整分割点的位置,继续进行二分。

        } else if (maxLeft1 > minRight2) {
            // 分割点过大,需要向左移动
            right = partition1 - 1;
        } else {
            // 分割点过小,需要向右移动
            left = partition1 + 1;
        }

完整代码如下:

double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    // 确保 nums1 的长度小于等于 nums2 的长度
    if (nums1Size > nums2Size) {
        return findMedianSortedArrays(nums2, nums2Size, nums1, nums1Size);
    }

    int m = nums1Size;
    int n = nums2Size;

    // 定义二分查找的边界
    int left = 0;
    int right = m;
    int halfLen = (m + n + 1) / 2; // 用于确定分割点的位置

    while (left <= right) {
        int partition1 = left + (right - left) / 2; // 在 nums1 中选择一个分割点
        int partition2 = halfLen - partition1; // 在 nums2 中根据分割点计算得到 nums2 的分割点

        // 获取分割点左侧的最大值和右侧的最小值
        int maxLeft1 = (partition1 == 0) ? INT_MIN : nums1[partition1 - 1];
        int minRight1 = (partition1 == m) ? INT_MAX : nums1[partition1];
        int maxLeft2 = (partition2 == 0) ? INT_MIN : nums2[partition2 - 1];
        int minRight2 = (partition2 == n) ? INT_MAX : nums2[partition2];

        if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
            // 分割点合理,根据数组总长度的奇偶性计算中位数
            if ((m + n) % 2 == 0) {
                return (double)(fmax(maxLeft1, maxLeft2) + fmin(minRight1, minRight2)) / 2.0;
            } else {
                return (double)fmax(maxLeft1, maxLeft2);
            }
        } else if (maxLeft1 > minRight2) {
            // 分割点过大,需要向左移动
            right = partition1 - 1;
        } else {
            // 分割点过小,需要向右移动
            left = partition1 + 1;
        }
    }

    // 如果程序走到这里,说明输入的数组不满足要求,返回一个无效的中位数
    return -1.0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值