https://leetcode.cn/problems/median-of-two-sorted-arrays/description/?envType=study-plan-v2&envId=top-100-liked
在日常工程和算法学习中,查找两个有序数组的中位数是一个经典的面试题。虽然简单理解上可以直接合并数组再取中位数,但这样做的时间复杂度为 O(m + n),不符合题目的要求。为了实现O(log(m + n))的性能,必须用到更巧妙的二分查找技巧。本文将带你详解这一算法思想,实现高效的解决方案。
题目描述:
给定两个升序数列 nums1
和 nums2
,求这两个数组合并后的中位数。
示例:
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
要求:
关键在于采用二分查找思想,在两个数组中找到满足条件的“分割线”位置,从而快速确定中位数。
选择较短的数组(为减少二分次数),在其范围内进行二分。
在nums1
的某个位置i
进行猜测:
i
表示数组1在某位置的分界线。
由i
和另外数组nums2
的对应分界线j
决定:
j = (总长度 + 1) // 2 - i
检查:
nums1[i-1] <= nums2[j]
,以及nums2[j-1] <= nums1[i]
若满足条件,意味着找到了正确的分割线。
根据总长度的奇偶,从左右边界和分割线的元素计算中位数。
public class MedianOfTwoSortedArrays {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 让 nums1 成为较短数组
if (nums1.length > nums2.length) {
int[] tmp = nums1; nums1 = nums2; nums2 = tmp;
}
int m = nums1.length;
int n = nums2.length;
int halfLen = (m + n + 1) / 2;
int left = 0, right = m;
while (left <= right) {
int i = left + (right - left) / 2;
int j = halfLen - i;
int maxLeftA = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1];
int minRightA = (i == m) ? Integer.MAX_VALUE : nums1[i];
int maxLeftB = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1];
int minRightB = (j == n) ? Integer.MAX_VALUE : nums2[j];
if (maxLeftA <= minRightB && maxLeftB <= minRightA) {
if ((m + n) % 2 == 0) {
return (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2.0;
} else {
return Math.max(maxLeftA, maxLeftB);
}
} else if (maxLeftA > minRightB) {
right = i - 1;
} else {
left = i + 1;
}
}
throw new IllegalArgumentException("Invalid input");
}
}
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
const findMedianSortedArrays = function(nums1, nums2) {
// 保证 nums1 为较短数组
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1];
}
const m = nums1.length;
const n = nums2.length;
const halfLen = Math.floor((m + n + 1) / 2);
let left = 0;
let right = m;
while (left <= right) {
const i = Math.floor((left + right) / 2);
const j = halfLen - i;
const maxLeftA = (i === 0) ? -Infinity : nums1[i - 1];
const minRightA = (i === m) ? Infinity : nums1[i];
const maxLeftB = (j === 0) ? -Infinity : nums2[j - 1];
const minRightB = (j === n) ? Infinity : nums2[j];
if (maxLeftA <= minRightB && maxLeftB <= minRightA) {
if ((m + n) % 2 === 0) {
return (Math.max(maxLeftA, maxLeftB) + Math.min(minRightA, minRightB)) / 2;
} else {
return Math.max(maxLeftA, maxLeftB);
}
} else if (maxLeftA > minRightB) {
right = i - 1;
} else {
left = i + 1;
}
}
throw new Error("Invalid input");
};
这个问题的核心是二分查找思想,理解它可以帮助你攻克许多高级算法题。希望本教程能帮助你理解、掌握这道经典题的思路和实现方法。祝你在算法的道路上越走越远!
在算法与数据结构的学习中,寻找两个正序数组的中位数是一个经典的面试题。它考察了我们对数组操作和二分查找的理解。本文将详细介绍如何解决这个问题,并提供Java和JavaScript的实现代码。
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的中位数。要求算法的时间复杂度为 O(log (m+n))。
输入: nums1 = [1,3]
, nums2 = [2]
输出: 2.00000
解释:合并数组 = [1,2,3] ,中位数为 2。
输入: nums1 = [1,2]
, nums2 = [3,4]
输出: 2.50000
解释:合并数组 = [1,2,3,4] ,中位数为 (2 + 3) / 2 = 2.5。
为了在 O(log (m+n)) 的时间复杂度内找到中位数,我们可以使用二分查找法。具体步骤如下:
nums1
的长度大于 nums2
,则交换它们。imin
和 imax
,分别为 0
和 m
(nums1
的长度)。i
和 j
,分别为 nums1
和 nums2
的分割点。i
和 j
的值,调整 imin
和 imax
,以确保左边的最大值小于等于右边的最小值。以下是使用二分查找法解决寻找两个正序数组中位数的Java代码:
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int m = nums1.length;
int n = nums2.length;
int imin = 0, imax = m, halfLen = (m + n + 1) / 2;
while (imin <= imax) {
int i = (imin + imax) / 2;
int j = halfLen - i;
if (i < m && nums2[j - 1] > nums1[i]) {
imin = i + 1; // i is too small
} else if (i > 0 && nums1[i - 1] > nums2[j]) {
imax = i - 1; // i is too big
} else {
// i is perfect
int 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; // odd
int 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.0; // even
}
}
return 0.0; // should never reach here
}
}
以下是使用二分查找法解决寻找两个正序数组中位数的JavaScript代码:
var findMedianSortedArrays = function(nums1, nums2) {
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1]; // 确保 nums1 是较小的数组
}
const m = nums1.length;
const n = nums2.length;
let imin = 0, imax = m, halfLen = Math.floor((m + n + 1) / 2);
while (imin <= imax) {
const i = Math.floor((imin + imax) / 2);
const j = halfLen - i;
if (i < m && nums2[j - 1] > nums1[i]) {
imin = i + 1; // i is too small
} else if (i > 0 && nums1[i - 1] > nums2[j]) {
imax = i - 1; // i is too big
} else {
// i is perfect
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; // odd
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.0; // even
}
}
return 0.0; // should never reach here
};
寻找两个正序数组的中位数是一个经典的算法题,适合用来练习二分查找的应用。通过本文的介绍,我们了解了如何使用二分查找来高效地计算中位数,并提供了Java和JavaScript的实现代码。希望这篇博客能帮助你更好地理解寻找两个正序数组中位数的问题及其解决方案。
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的中位数。
算法的时间复杂度应该为 O(log (m+n)) 。
示例:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
这道题的难点在于如何在 O(log (m+n)) 的时间复杂度内找到中位数。 我们可以使用二分查找来解决这个问题。
题目要求时间复杂度为 O(log (m+n)),这暗示我们需要使用二分查找。 二分查找是一种高效的查找算法,可以在有序数组中快速找到目标元素。
寻找两个正序数组的中位数可以转化为寻找第 k 小的数的问题。
m + n
是奇数,则中位数是第 (m + n + 1) / 2
小的数。m + n
是偶数,则中位数是第 (m + n) / 2
小的数和第 (m + n) / 2 + 1
小的数的平均值。因此,我们可以实现一个函数 findKth(nums1, nums2, k)
,用于寻找两个正序数组中第 k 小的数。
nums1[k/2 - 1] < nums2[k/2 - 1]
,则说明 nums1
的前 k/2
个元素不可能是第 k 小的数,可以排除。nums2
的前 k/2
个元素不可能是第 k 小的数,可以排除。findKth
函数,继续寻找第 k 小的数。nums1
或 nums2
为空,则直接返回另一个数组的第 k 个元素。k = 1
,则返回 nums1[0]
和 nums2[0]
中的最小值。在二分查找的过程中,需要注意处理一些边界情况,例如数组为空、k 的值超出数组长度等。
public class FindMedianSortedArrays {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int totalLength = m + n;
if (totalLength % 2 == 1) {
return findKth(nums1, 0, nums2, 0, (totalLength + 1) / 2);
} else {
return (findKth(nums1, 0, nums2, 0, totalLength / 2) +
findKth(nums1, 0, nums2, 0, totalLength / 2 + 1)) / 2.0;
}
}
private double findKth(int[] nums1, int start1, int[] nums2, int start2, int k) {
int len1 = nums1.length - start1;
int len2 = nums2.length - start2;
// 保证 nums1 是较短的数组
if (len1 > len2) {
return findKth(nums2, start2, nums1, start1, k);
}
if (len1 == 0) {
return nums2[start2 + k - 1];
}
if (k == 1) {
return Math.min(nums1[start1], nums2[start2]);
}
int i = Math.min(len1, k / 2);
int j = Math.min(len2, k / 2);
if (nums1[start1 + i - 1] < nums2[start2 + j - 1]) {
return findKth(nums1, start1 + i, nums2, start2, k - i);
} else {
return findKth(nums1, start1, nums2, start2 + j, k - j);
}
}
public static void main(String[] args) {
FindMedianSortedArrays fmsa = new FindMedianSortedArrays();
int[] nums1 = {1, 3};
int[] nums2 = {2};
System.out.println(fmsa.findMedianSortedArrays(nums1, nums2)); // 输出: 2.0
int[] nums3 = {1, 2};
int[] nums4 = {3, 4};
System.out.println(fmsa.findMedianSortedArrays(nums3, nums4)); // 输出: 2.5
}
}
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var findMedianSortedArrays = function(nums1, nums2) {
const m = nums1.length;
const n = nums2.length;
const totalLength = m + n;
if (totalLength % 2 === 1) {
return findKth(nums1, 0, nums2, 0, Math.floor((totalLength + 1) / 2));
} else {
return (findKth(nums1, 0, nums2, 0, totalLength / 2) +
findKth(nums1, 0, nums2, 0, totalLength / 2 + 1)) / 2;
}
};
function findKth(nums1, start1, nums2, start2, k) {
const len1 = nums1.length - start1;
const len2 = nums2.length - start2;
// 保证 nums1 是较短的数组
if (len1 > len2) {
return findKth(nums2, start2, nums1, start1, k);
}
if (len1 === 0) {
return nums2[start2 + k - 1];
}
if (k === 1) {
return Math.min(nums1[start1] || Infinity, nums2[start2] || Infinity);
}
const i = Math.min(len1, Math.floor(k / 2));
const j = Math.min(len2, Math.floor(k / 2));
if (nums1[start1 + i - 1] < nums2[start2 + j - 1]) {
return findKth(nums1, start1 + i, nums2, start2, k - i);
} else {
return findKth(nums1, start1, nums2, start2 + j, k - j);
}
}
// 示例
console.log(findMedianSortedArrays([1, 3], [2])); // 输出: 2.0
console.log(findMedianSortedArrays([1, 2], [3, 4])); // 输出: 2.5
这道题是一道非常经典的题目,它考察了对二分查找的理解和应用。 通过这道题,可以掌握如何使用二分查找来解决一些特定的问题,例如寻找两个有序数组的中位数。
扩展:
希望这篇博客能够帮助你理解力扣 4 题“寻找两个正序数组的中位数”的解法,并对二分查找有一个更深入的认识。 祝你刷题愉快!
目录:
问题描述
Java解法
JavaScript解法
二分查找基础
总结
问题描述
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。算法的时间复杂度应该为 O(log (m+n))。
Java解法
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
if (m > n) {
return findMedianSortedArrays(nums2, nums1);
}
int left = 0, right = m;
while (left <= right) {
int partitionX = (left + right) / 2;
int partitionY = (m + n + 1) / 2 - partitionX;
int maxLeftX = (partitionX == 0) ? Integer.MIN_VALUE : nums1[partitionX - 1];
int minRightX = (partitionX == m) ? Integer.MAX_VALUE : nums1[partitionX];
int maxLeftY = (partitionY == 0) ? Integer.MIN_VALUE : nums2[partitionY - 1];
int minRightY = (partitionY == n) ? Integer.MAX_VALUE : nums2[partitionY];
if (maxLeftX <= minRightY && maxLeftY <= minRightX) {
if ((m + n) % 2 == 0) {
return (Math.max(maxLeftX, maxLeftY) + Math.min(minRightX, minRightY)) / 2.0;
} else {
return Math.max(maxLeftX, maxLeftY);
}
} else if (maxLeftX > minRightY) {
right = partitionX - 1;
} else {
left = partitionX + 1;
}
}
throw new IllegalArgumentException();
}
}
解释:
首先比较两个数组的长度,将较短的数组作为 nums1
,较长的数组作为 nums2
。
使用二分查找在 nums1
中找到一个分割点 partitionX
,使得左边的元素都小于等于右边的元素。
根据 partitionX
计算出 nums2
中的分割点 partitionY
。
检查左边最大值和右边最小值的关系,如果满足条件,则可以计算出中位数。
如果不满足条件,则根据左右两边的关系调整二分查找的范围。
JavaScript解法
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var findMedianSortedArrays = function(nums1, nums2) {
let m = nums1.length, n = nums2.length;
if (m > n) {
return findMedianSortedArrays(nums2, nums1);
}
let left = 0, right = m;
while (left <= right) {
let partitionX = Math.floor((left + right) / 2);
let partitionY = Math.floor((m + n + 1) / 2 - partitionX);
let maxLeftX = (partitionX === 0) ? -Infinity : nums1[partitionX - 1];
let minRightX = (partitionX === m) ? Infinity : nums1[partitionX];
let maxLeftY = (partitionY === 0) ? -Infinity : nums2[partitionY - 1];
let minRightY = (partitionY === n) ? Infinity : nums2[partitionY];
if (maxLeftX <= minRightY && maxLeftY <= minRightX) {
if ((m + n) % 2 === 0) {
return (Math.max(maxLeftX, maxLeftY) + Math.min(minRightX, minRightY)) / 2;
} else {
return Math.max(maxLeftX, maxLeftY);
}
} else if (maxLeftX > minRightY) {
right = partitionX - 1;
} else {
left = partitionX + 1;
}
}
throw new Error("Invalid input");
};
Java和JavaScript解法的思路是一致的,都使用二分查找来解决这个问题。主要区别在于语法和一些细节上的差异。
具体做法如下:
nums1
,较长的数组作为 nums2
。nums1
中找到一个分割点 partitionX
,使得左边的元素都小于等于右边的元素。partitionX
计算出 nums2
中的分割点 partitionY
。这种做法可以保证时间复杂度为 O(log (m+n)),满足题目要求。
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。需要找出并返回这两个正序数组的中位数,且算法的时间复杂度应该为 O ( l o g ( m + n ) ) O(log (m + n)) O(log(m+n))。
nums1 = [1, 3]
,nums2 = [2]
2.00000
[1, 2, 3]
,中位数是 2
。nums1 = [1, 2]
,nums2 = [3, 4]
2.50000
[1, 2, 3, 4]
,中位数是 (2 + 3) / 2 = 2.5
。nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-10^6 <= nums1[i], nums2[i] <= 10^6
最容易想到的方法是将两个数组合并成一个新的有序数组,然后根据数组长度的奇偶性来计算中位数。
为了满足时间复杂度 O ( l o g ( m + n ) ) O(log (m + n)) O(log(m+n)) 的要求,我们可以使用二分查找法。核心思想是通过二分查找确定一个合适的分割点,将两个数组分成左右两部分,使得左半部分的元素个数等于或比右半部分多一个,并且左半部分的所有元素都小于等于右半部分的所有元素。
nums1
是较短的数组,这样可以减少二分查找的范围。nums1
进行二分查找,确定分割点 i
。i
计算 nums2
的分割点 j
,使得 i + j = (m + n + 1) / 2
。nums1[i - 1] <= nums2[j]
且 nums2[j - 1] <= nums1[i]
。class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int[] merged = new int[m + n];
int i = 0, j = 0, k = 0;
while (i < m && j < n) {
if (nums1[i] < nums2[j]) {
merged[k++] = nums1[i++];
} else {
merged[k++] = nums2[j++];
}
}
while (i < m) {
merged[k++] = nums1[i++];
}
while (j < n) {
merged[k++] = nums2[j++];
}
if ((m + n) % 2 == 1) {
return merged[(m + n) / 2];
} else {
return (merged[(m + n) / 2 - 1] + merged[(m + n) / 2]) / 2.0;
}
}
}
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.length;
int n = nums2.length;
int left = 0, right = m;
while (left <= right) {
int i = (left + right) / 2;
int j = (m + n + 1) / 2 - i;
int maxLeft1 = (i == 0) ? Integer.MIN_VALUE : nums1[i - 1];
int minRight1 = (i == m) ? Integer.MAX_VALUE : nums1[i];
int maxLeft2 = (j == 0) ? Integer.MIN_VALUE : nums2[j - 1];
int minRight2 = (j == n) ? Integer.MAX_VALUE : nums2[j];
if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
if ((m + n) % 2 == 1) {
return Math.max(maxLeft1, maxLeft2);
} else {
return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2.0;
}
} else if (maxLeft1 > minRight2) {
right = i - 1;
} else {
left = i + 1;
}
}
throw new IllegalArgumentException("Input arrays are not sorted.");
}
}
var findMedianSortedArrays = function(nums1, nums2) {
let m = nums1.length;
let n = nums2.length;
let merged = [];
let i = 0, j = 0;
while (i < m && j < n) {
if (nums1[i] < nums2[j]) {
merged.push(nums1[i++]);
} else {
merged.push(nums2[j++]);
}
}
while (i < m) {
merged.push(nums1[i++]);
}
while (j < n) {
merged.push(nums2[j++]);
}
if ((m + n) % 2 === 1) {
return merged[Math.floor((m + n) / 2)];
} else {
return (merged[Math.floor((m + n) / 2) - 1] + merged[Math.floor((m + n) / 2)]) / 2;
}
};
var findMedianSortedArrays = function(nums1, nums2) {
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
let m = nums1.length;
let n = nums2.length;
let left = 0, right = m;
while (left <= right) {
let i = Math.floor((left + right) / 2);
let j = Math.floor((m + n + 1) / 2) - i;
let maxLeft1 = (i === 0) ? -Infinity : nums1[i - 1];
let minRight1 = (i === m) ? Infinity : nums1[i];
let maxLeft2 = (j === 0) ? -Infinity : nums2[j - 1];
let minRight2 = (j === n) ? Infinity : nums2[j];
if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
if ((m + n) % 2 === 1) {
return Math.max(maxLeft1, maxLeft2);
} else {
return (Math.max(maxLeft1, maxLeft2) + Math.min(minRight1, minRight2)) / 2;
}
} else if (maxLeft1 > minRight2) {
right = i - 1;
} else {
left = i + 1;
}
}
};
本题有简单合并排序法和二分查找法两种解法。简单合并排序法容易理解,但时间复杂度较高;二分查找法虽然实现起来较复杂,但能满足 O ( l o g ( m + n ) ) O(log (m + n)) O(log(m+n)) 的时间复杂度要求。对于新手来说,理解二分查找法的思路和实现是关键。希望通过这篇博客,你能掌握本题的解题思路和代码实现。
在力扣(LeetCode)的题目中,4. 寻找两个正序数组的中位数是一个中等难度的算法问题。题目要求我们找出两个已排序数组合并后的中位数。由于两个数组的长度可能不同,我们需要一种高效的方法来解决这个问题,其时间复杂度应为O(log(m+n))。
为了找到两个数组合并后的中位数,我们可以考虑以下策略:
我们可以使用二分查找的方法来解决这个问题。具体步骤如下:
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
if (m > n) {
return findMedianSortedArrays(nums2, nums1);
}
int imin = 0, imax = m, halfLen = (m + n + 1) / 2;
while (imin <= imax) {
int i = (imin + imax) / 2;
int j = halfLen - i;
if (i < m && nums2[j - 1] > nums1[i]) {
// i is too small, must increase it
imin = i + 1;
} else if (i > 0 && nums1[i - 1] > nums2[j]) {
// i is too big, must decrease it
imax = i - 1;
} else { // i is perfect
int 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; }
int 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.0;
}
}
return 0.0;
}
JavaScript的实现与Java类似,也是使用二分查找的方法来解决这个问题。
function findMedianSortedArrays(nums1, nums2) {
let m = nums1.length, n = nums2.length;
if (m > n) {
return findMedianSortedArrays(nums2, nums1);
}
let imin = 0, imax = m, halfLen = Math.ceil((m + n + 1) / 2);
while (imin <= imax) {
let i = Math.floor((imin + imax) / 2);
let j = halfLen - i;
if (i < m && nums2[j - 1] > nums1[i]) {
imin = i + 1;
} else if (i > 0 && nums1[i - 1] > nums2[j]) {
imax = i - 1;
} 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.0;
}
}
return 0.0;
}
本文介绍了如何使用Java和JavaScript解决寻找两个正序数组的中位数问题。通过使用二分查找的方法,我们可以有效地找到两个数组合并后的中位数,且时间复杂度为O(log(min(m, n)))。
给定两个大小分别为 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((m+n)log(m+n)),不满足题目要求
核心思想:将问题转化为寻找两个数组中第k小的元素
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var findMedianSortedArrays = function(nums1, nums2) {
// 确保nums1是较短的数组
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1];
}
const m = nums1.length;
const n = nums2.length;
const totalLeft = Math.floor((m + n + 1) / 2);
let left = 0;
let right = m;
while (left < right) {
const i = left + Math.floor((right - left + 1) / 2);
const j = totalLeft - i;
if (nums1[i - 1] > nums2[j]) {
right = i - 1;
} else {
left = i;
}
}
const i = left;
const j = totalLeft - i;
const nums1LeftMax = i === 0 ? -Infinity : nums1[i - 1];
const nums1RightMin = i === m ? Infinity : nums1[i];
const nums2LeftMax = j === 0 ? -Infinity : nums2[j - 1];
const nums2RightMin = j === n ? Infinity : nums2[j];
if ((m + n) % 2 === 1) {
return Math.max(nums1LeftMax, nums2LeftMax);
} else {
return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2;
}
};
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 确保nums1是较短的数组
if (nums1.length > nums2.length) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int m = nums1.length;
int n = nums2.length;
int totalLeft = (m + n + 1) / 2;
int left = 0;
int right = m;
while (left < right) {
int i = left + (right - left + 1) / 2;
int j = totalLeft - i;
if (nums1[i - 1] > nums2[j]) {
right = i - 1;
} else {
left = i;
}
}
int i = left;
int j = totalLeft - i;
int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
int nums1RightMin = i == m ? Integer.MAX_VALUE : nums1[i];
int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
int nums2RightMin = j == n ? Integer.MAX_VALUE : nums2[j];
if ((m + n) % 2 == 1) {
return Math.max(nums1LeftMax, nums2LeftMax);
} else {
return (Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin)) / 2.0;
}
}
}
nums1: [a1, a2, a3, a4]
nums2: [b1, b2, b3, b4, b5, b6]
理想分割:
nums1左半部: [a1, a2] | nums1右半部: [a3, a4]
nums2左半部: [b1, b2, b3] | nums2右半部: [b4, b5, b6]
满足条件:
a2 <= b4 且 b3 <= a3
掌握这种二分查找的变种应用,能够解决许多类似的查找问题。建议通过画图来理解分割点的选择过程,并尝试解决类似的题目来巩固这一知识点。
给定两个大小分别为 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((m+n)log(m+n)),不满足题目要求
缺点:时间复杂度为O(m+n),仍不满足题目要求
优点:时间复杂度为O(log(min(m,n))),满足题目要求
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 确保nums1是较短的数组
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.length;
int n = nums2.length;
int left = 0, right = m;
while (left <= right) {
// 在nums1中二分查找分割线
int partitionX = (left + right) / 2;
int partitionY = (m + n + 1) / 2 - partitionX;
// 处理边界情况
int maxLeftX = (partitionX == 0) ? Integer.MIN_VALUE : nums1[partitionX - 1];
int minRightX = (partitionX == m) ? Integer.MAX_VALUE : nums1[partitionX];
int maxLeftY = (partitionY == 0) ? Integer.MIN_VALUE : nums2[partitionY - 1];
int minRightY = (partitionY == n) ? Integer.MAX_VALUE : nums2[partitionY];
if (maxLeftX <= minRightY && maxLeftY <= minRightX) {
// 找到正确的分割线
if ((m + n) % 2 == 0) {
return (Math.max(maxLeftX, maxLeftY) + Math.min(minRightX, minRightY)) / 2.0;
} else {
return Math.max(maxLeftX, maxLeftY);
}
} else if (maxLeftX > minRightY) {
right = partitionX - 1;
} else {
left = partitionX + 1;
}
}
throw new IllegalArgumentException("Input arrays are not sorted");
}
}
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var findMedianSortedArrays = function(nums1, nums2) {
// 确保nums1是较短的数组
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
const m = nums1.length;
const n = nums2.length;
let left = 0, right = m;
while (left <= right) {
const partitionX = Math.floor((left + right) / 2);
const partitionY = Math.floor((m + n + 1) / 2) - partitionX;
// 处理边界情况
const maxLeftX = (partitionX === 0) ? -Infinity : nums1[partitionX - 1];
const minRightX = (partitionX === m) ? Infinity : nums1[partitionX];
const maxLeftY = (partitionY === 0) ? -Infinity : nums2[partitionY - 1];
const minRightY = (partitionY === n) ? Infinity : nums2[partitionY];
if (maxLeftX <= minRightY && maxLeftY <= minRightX) {
// 找到正确的分割线
if ((m + n) % 2 === 0) {
return (Math.max(maxLeftX, maxLeftY) + Math.min(minRightX, minRightY)) / 2;
} else {
return Math.max(maxLeftX, maxLeftY);
}
} else if (maxLeftX > minRightY) {
right = partitionX - 1;
} else {
left = partitionX + 1;
}
}
throw new Error("Input arrays are not sorted");
};
寻找两个有序数组中位数的问题展示了如何将经典二分查找算法应用于更复杂的问题场景。关键点在于:
Java和JavaScript的实现非常相似,主要区别在于:
Integer.MIN_VALUE/MAX_VALUE
,JavaScript使用Infinity
对于新手来说,建议:
这种二分查找的变种思想可以应用于许多需要高效搜索的问题,如寻找第K小的元素、矩阵搜索等。
题目:给定两个大小分别为 m
和 n
的有序数组 nums1
和 nums2
,找出并返回它们的中位数。要求算法的时间复杂度为 O(log(m + n))。
示例:
nums1 = [1,3], nums2 = [2]
2.00000
[1,2,3]
,中位数是中间元素 2
。nums1 = [1,2], nums2 = [3,4]
2.50000
[1,2,3,4]
,中位数是中间两个元素的平均值 (2 + 3)/2 = 2.5
。关键点:
合并数组的实现:
问题:
m
或 n
接近 1000
),合并数组会消耗大量时间和空间。中位数的定义:
(m + n + 1) / 2
个元素。(m + n) / 2
和第 (m + n) / 2 + 1
个元素的平均值。关键问题:如何在 O(log(m + n)) 时间复杂度下找到第 k
小的元素?
二分查找策略:
k
小元素的部分。total = m + n
。total
决定需要找的第 k
小元素的位置。k
为 1
时,直接取最小值。k
小元素。k = 1
时,取两个数组当前起始位置的较小值。nums1[i - 1]
和 nums2[j - 1]
,确定哪一部分可以被排除。var findMedianSortedArrays = function(nums1, nums2) {
const m = nums1.length, n = nums2.length;
if (m > n) return findMedianSortedArrays(nums2, nums1); // 确保nums1是较短的数组
const total = m + n;
const half = Math.floor((total) / 2);
let l = 0, r = m;
while (l <= r) {
const i = Math.floor((l + r) / 2); // nums1的分割点
const j = half - i; // nums2的分割点
// 如果i太小,需要向右调整
if (i < m && nums2[j - 1] > nums1[i]) {
l = i + 1;
}
// 如果i太大,需要向左调整
else if (i > 0 && nums1[i - 1] > nums2[j]) {
r = i - 1;
}
// 找到正确分割点
else {
let maxLeft;
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 (total % 2 === 1) return maxLeft;
let minRight;
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;
}
}
return 0.0;
};
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.length, n = nums2.length;
int total = m + n;
int half = (total) / 2;
int l = 0, r = m;
while (l <= r) {
int i = (l + r) / 2; // nums1的分割点
int j = half - i; // nums2的分割点
// 如果i太小,需要向右调整
if (i < m && nums2[j - 1] > nums1[i]) {
l = i + 1;
}
// 如果i太大,需要向左调整
else if (i > 0 && nums1[i - 1] > nums2[j]) {
r = i - 1;
}
// 找到正确分割点
else {
int maxLeft;
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 (total % 2 == 1) return maxLeft;
int minRight;
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.0;
}
}
return 0.0;
}
}
i + j = (m + n + 1) / 2
(奇数情况)或 i + j = (m + n) / 2
(偶数情况)。i
的位置,逐步逼近中位数。k
小元素时,此方法非常有效。通过本题,可以掌握二分查找在处理有序数组问题中的高级应用,理解如何通过分治思想优化算法的时间复杂度。
给定两个有序数组 nums1
和 nums2
,长度分别为 m
和 n
,要求时间复杂度 O(log(m+n))
找到合并后的中位数。
示例:
输入:nums1 = [1,3], nums2 = [2]
→ 输出:2.0
输入:nums1 = [1,2], nums2 = [3,4]
→ 输出:2.5
合并数组后取中位数的时间复杂度为 O(m+n)
,无法满足题目要求。我们需要更高效的分治策略。
将问题转化为寻找第k小元素,通过每次排除 k/2
个元素实现对数级时间复杂度。
无论总长度奇偶,都寻找两个分割点:
k = (m+n+1)/2
k1 = (m+n)/2
, k2 = (m+n)/2 + 1
nums1
是较短的数组(减少计算量)nums1
中二分查找分割线位置 i
nums2
的分割线位置 j = k - i
nums1[i-1] ≤ nums2[j]
nums2[j-1] ≤ nums1[i]
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length, n = nums2.length;
if (m > n) return findMedianSortedArrays(nums2, nums1); // 确保nums1较短
int k = (m + n + 1) / 2;
int left = 0, right = m;
while (left < right) {
int i = left + (right - left) / 2;
int j = k - i;
if (nums1[i] < nums2[j-1]) {
left = i + 1;
} else {
right = i;
}
}
int i = left, j = k - i;
int c1 = Math.max(i > 0 ? nums1[i-1] : Integer.MIN_VALUE,
j > 0 ? nums2[j-1] : Integer.MIN_VALUE);
if ((m + n) % 2 == 1) return c1;
int c2 = Math.min(i < m ? nums1[i] : Integer.MAX_VALUE,
j < n ? nums2[j] : Integer.MAX_VALUE);
return (c1 + c2) / 2.0;
}
}
function findMedianSortedArrays(nums1, nums2) {
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1];
}
const m = nums1.length, n = nums2.length;
const total = m + n;
const k = Math.floor((total + 1) / 2);
let left = 0, right = m;
while (left <= right) {
const i = Math.floor((left + right) / 2);
const j = k - i;
const maxLeft1 = i === 0 ? -Infinity : nums1[i-1];
const minRight1 = i === m ? Infinity : nums1[i];
const maxLeft2 = j === 0 ? -Infinity : nums2[j-1];
const minRight2 = j === n ? Infinity : nums2[j];
if (maxLeft1 <= minRight2 && maxLeft2 <= minRight1) {
const maxLeft = Math.max(maxLeft1, maxLeft2);
if (total % 2 === 1) return maxLeft;
const minRight = Math.min(minRight1, minRight2);
return (maxLeft + minRight) / 2;
} else if (maxLeft1 > minRight2) {
right = i - 1;
} else {
left = i + 1;
}
}
return 0;
}
场景 | 处理方案 |
---|---|
空数组 | 直接返回另一个数组的中位数 |
全左分割 | 取另一数组的右侧元素 |
全右分割 | 取另一数组的左侧元素 |
元素交叉 | 比较相邻元素确定正确分割线 |
nums1
为较短数组通过这种分治策略,我们成功将时间复杂度降低到对数级别,完美满足题目要求。理解这个算法需要多画图模拟分割线移动过程,建议通过示例数组手动走一遍算法流程加深理解。
给定两个正序(升序)数组 nums1
和 nums2
,长度分别为 m
和 n
。找出这两个数组的中位数,要求算法时间复杂度为 O(log(m+n))。
示例:
输入:nums1 = [1,3], nums2 =
输出:2.0(合并数组为[1,2,3],中位数为第二个元素)
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.5(合并数组为[1,2,3,4],中位数为(2+3)/2)
将问题转化为寻找两个数组中第k小的元素,通过每次排除一半不可能的元素实现对数复杂度。
步骤:
totalLen = m + n
,确定中位数位置 k = (totalLen + 1)/2
。k/2
个元素:
nums1[k/2-1] < nums2[k/2-1]
,则排除 nums1
的前 k/2
个元素。nums2
的前 k/2
个元素。k
为 k - 排除元素个数
,递归直到 k=1
。class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int total = nums1.length + nums2.length;
if (total % 2 == 1) {
return getKthElement(nums1, nums2, total / 2 + 1);
} else {
return (getKthElement(nums1, nums2, total / 2)
+ getKthElement(nums1, nums2, total / 2 + 1)) / 2.0;
}
}
private double getKthElement(int[] nums1, int[] nums2, int k) {
int i = 0, j = 0;
while (true) {
if (i == nums1.length) return nums2[j + k - 1];
if (j == nums2.length) return nums1[i + k - 1];
if (k == 1) return Math.min(nums1[i], nums2[j]);
int half = k / 2;
int newI = Math.min(i + half, nums1.length) - 1;
int newJ = Math.min(j + half, nums2.length) - 1;
if (nums1[newI] <= nums2[newJ]) {
k -= (newI - i + 1);
i = newI + 1;
} else {
k -= (newJ - j + 1);
j = newJ + 1;
}
}
}
}
k
个元素。k=1
时返回两数组当前最小值。Math.min
防止越界,确保排除的元素个数正确。var findMedianSortedArrays = function(nums1, nums2) {
const total = nums1.length + nums2.length;
const k = Math.floor((total + 1) / 2);
if (total % 2 === 1) {
return getKthElement(nums1, nums2, k);
} else {
return (getKthElement(nums1, nums2, k)
+ getKthElement(nums1, nums2, k + 1)) / 2;
}
};
function getKthElement(nums1, nums2, k) {
let i = 0, j = 0;
while (true) {
if (i === nums1.length) return nums2[j + k - 1];
if (j === nums2.length) return nums1[i + k - 1];
if (k === 1) return Math.min(nums1[i], nums2[j]);
const half = Math.floor(k / 2);
const newI = Math.min(i + half, nums1.length) - 1;
const newJ = Math.min(j + half, nums2.length) - 1;
if (nums1[newI] <= nums2[newJ]) {
k -= (newI - i + 1);
i = newI + 1;
} else {
k -= (newJ - j + 1);
j = newJ + 1;
}
}
}
Math.min
确保索引不越界。LeetCode题解:二分法思路与实现
二分查找法详细推导步骤
Java与JavaScript代码实现对比