解题思路:
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;
}