BFE.devAI解答 - JavaScript 代码题
136. 两个已排序数组的中位数
解法一:归并排序
由于两个数组已经有序,可以利用归并排序的思路进行合并,并找到中位数。
具体来说,可以用两个指针分别指向两个数组的起始位置,每次比较两个指针所在位置的数字大小,较小的那个加入合并后的数组中,并将对应指针向后移动一位。重复这个过程,直到合并后的数组长度达到中位数位置。如果数字总数是奇数,中位数是合并后数组的最后一个数字;如果是偶数,中位数是合并后数组的最后两个数字的平均数。
时间复杂度:O(m+n),其中 m 和 n 分别为两个数组的长度。
空间复杂度:O(m+n),需要开辟一个新的数组来存储合并后的数组。
代码如下:
function median(nums1, nums2) {
const merged = [];
let i = 0,
j = 0;
while (i < nums1.length && j < nums2.length) {
if (nums1[i] <= nums2[j]) {
merged.push(nums1[i]);
i++;
} else {
merged.push(nums2[j]);
j++;
}
}
while (i < nums1.length) {
merged.push(nums1[i]);
i++;
}
while (j < nums2.length) {
merged.push(nums2[j]);
j++;
}
const len = merged.length;
return len % 2 == 0 ? (merged[len / 2 - 1] + merged[len / 2]) / 2 : merged[Math.floor(len / 2)];
}
时间复杂度已经是最优解了,但是空间复杂度较高。考虑优化一下空间复杂度。
解法二:二分查找
如果要求的是中位数,那么可以将两个已排序的数组合并为一个排序数组后,直接找到中位数。但是这种方法需要 O(m+n) 的时间和空间复杂度。
由于两个数组已经有序,可以考虑使用二分查找的方法。假设中位数是 m,那么要找到 m,在两个数组中需要找到两个位置 i 和 j,这两个位置满足:
- 左数组 nums1 中 [0, i-1] 的数都小于 m;
- 右数组 nums1 中 [i, m-1] 的数都大于 m;
- 左数组 nums2 中 [0, j-1] 的数都小于 m;
- 右数组 nums2 中 [j, n-1] 的数都大于 m;
注意,i 和 j 可以为 0 和 n,这表示前一个数组或后一个数组为空。
根据条件可知,i,j的值范围均在 [0, m] 和 [0, n] 之间。所以可以通过二分查找从 nums1 中找到合适的 i,剩下的 j 可以求得。
假设 nums1 的长度为 m,nums2 的长度为 n。另 k = (m + n) / 2,当 k 为整数时,中位数即为 k 和 k+1 位置的数字的平均值;当 k 为奇数时,中位数即为第 k+1 个数字。
时间复杂度:O(log(min(m,n))),其中 m 和 n 分别为两个数组的长度。
空间复杂度:O(1),只需要常数级别的空间。
代码如下:
function median(nums1, nums2) {
const m = nums1.length,
n = nums2.length;
let iMin = 0,
iMax = m,
halfLen = Math.floor((m + n + 1) / 2);
while (iMin <= iMax) {
let i = Math.floor((iMin + iMax) / 2);
let j = halfLen - i;
if (i < iMax && nums2[j - 1] > nums1[i]) {
iMin = i + 1; // i 太小了,需要增大
} else if (i > iMin && nums1[i - 1] > nums2[j]) {
iMax = i - 1; // i 太大了,需要减小
} else {
let maxLeft = 0;
if (i == 0) {
maxLeft = nums2[j - 1];
} else if (j == 0) {
maxLeft = nums1[i - 1];
} else {
maxLeft = Math.max(nums1[i - 1], nums2[j - 1]);
}
if ((m + n) % 2 == 1) {
return maxLeft;
}
let minRight = 0;
if (i == m) {
minRight = nums2[j];
} else if (j == n) {
minRight = nums1[i];
} else {
minRight = Math.min(nums1[i], nums2[j]);
}
return (maxLeft + minRight) / 2;
}
}
}
综上所述,两种方法的时间复杂度是相同的,但空间复杂度上二分查找更优秀。