题目:
官方链接:
参考答案:
【新手入门】LeetCode 4. 寻找两个正序数组的中位数(Median of Two Sorted Arrays)详细解题指南
目录
- 引言
- 题目简介
- 如何在 O(log(m + n)) 时间复杂度内找到中位数?
- 解题核心:二分查找的思想
- 详细步骤与思路分析
- Java 实现代码
- JavaScript 实现代码
- 小结
- 相关阅读与参考资料
引言
在日常工程和算法学习中,查找两个有序数组的中位数是一个经典的面试题。虽然简单理解上可以直接合并数组再取中位数,但这样做的时间复杂度为 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
要求:
- 时间复杂度:O(log(m + n))
- 数组可能为空,但至少有一个元素(m + n >= 1)
如何在 O(log(m + n)) 时间复杂度内找到中位数?
关键在于采用二分查找思想,在两个数组中找到满足条件的“分割线”位置,从而快速确定中位数。
解题核心:二分查找的思想
- 将两个数组视为一条线上的两个区间。
- 在两个数组中找到一种“分割方式”,使得左边的元素都不大于右边的元素。
- 根据左右区域的元素个数,计算中位数。
详细步骤与思路分析
-
选择较短的数组(为减少二分次数),在其范围内进行二分。
-
在
nums1
的某个位置i
进行猜测:-
i
表示数组1在某位置的分界线。 -
由
i
和另外数组nums2
的对应分界线j
决定:j = (总长度 + 1) // 2 - i
-
-
检查:
nums1[i-1] <= nums2[j]
,以及nums2[j-1] <= nums1[i]
若满足条件,意味着找到了正确的分割线。
-
根据总长度的奇偶,从左右边界和分割线的元素计算中位数。
代码实现(Java 和 JavaScript)
Java 代码实现
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");
}
}
JavaScript 代码实现
/**
* @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");
};
小结
- 利用二分查找,选择恰当的“分割线”,可以在O(log(min(m, n)))时间内找到中位数。
- 关键点在于“保证每日查找满足左右条件”,保证找到正确分割线。
- 这也是一种“分治策略”的应用,思路巧妙且高效。
相关阅读与参考资料
- LeetCode 4. Median of Two Sorted Arrays
- “高效算法面试题揭秘”书籍中的区间查找技巧
- 官方题解分析
结束语
这个问题的核心是二分查找思想,理解它可以帮助你攻克许多高级算法题。希望本教程能帮助你理解、掌握这道经典题的思路和实现方法。祝你在算法的道路上越走越远!
寻找两个正序数组的中位数问题详解:Java与JavaScript实现
目录
引言
在算法与数据结构的学习中,寻找两个正序数组的中位数是一个经典的面试题。它考察了我们对数组操作和二分查找的理解。本文将详细介绍如何解决这个问题,并提供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 是较小的数组:如果
nums1
的长度大于nums2
,则交换它们。 - 定义二分查找的边界:设置
imin
和imax
,分别为0
和m
(nums1
的长度)。 - 进行二分查找:
- 计算
i
和j
,分别为nums1
和nums2
的分割点。 - 根据
i
和j
的值,调整imin
和imax
,以确保左边的最大值小于等于右边的最小值。
- 计算
- 计算中位数:
- 如果总长度为偶数,中位数为左边最大值和右边最小值的平均值。
- 如果总长度为奇数,中位数为左边的最大值。
Java实现
以下是使用二分查找法解决寻找两个正序数组中位数的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实现
以下是使用二分查找法解决寻找两个正序数组中位数的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的实现代码。希望这篇博客能帮助你更好地理解寻找两个正序数组中位数的问题及其解决方案。
力扣 4 题:寻找两个正序数组的中位数——二分查找详解(Java & JavaScript)
目录
- 题目描述
- 解题思路:二分查找
- 2.1 为什么使用二分查找
- 2.2 转化为寻找第 k 小的数
- 2.3 二分查找的步骤
- 2.4 处理边界情况
- Java 代码实现
- JavaScript 代码实现
- 复杂度分析
- 总结与扩展
1. 题目描述
给定两个大小分别为 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
2. 解题思路:二分查找
这道题的难点在于如何在 O(log (m+n)) 的时间复杂度内找到中位数。 我们可以使用二分查找来解决这个问题。
2.1 为什么使用二分查找
题目要求时间复杂度为 O(log (m+n)),这暗示我们需要使用二分查找。 二分查找是一种高效的查找算法,可以在有序数组中快速找到目标元素。
2.2 转化为寻找第 k 小的数
寻找两个正序数组的中位数可以转化为寻找第 k 小的数的问题。
- 如果
m + n
是奇数,则中位数是第(m + n + 1) / 2
小的数。 - 如果
m + n
是偶数,则中位数是第(m + n) / 2
小的数和第(m + n) / 2 + 1
小的数的平均值。
因此,我们可以实现一个函数 findKth(nums1, nums2, k)
,用于寻找两个正序数组中第 k 小的数。
2.3 二分查找的步骤
- 比较两个数组的第 k/2 个元素:
- 如果
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]
中的最小值。
- 如果
2.4 处理边界情况
在二分查找的过程中,需要注意处理一些边界情况,例如数组为空、k 的值超出数组长度等。
3. Java 代码实现
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
}
}
4. JavaScript 代码实现
/**
* @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
5. 复杂度分析
- 时间复杂度: O(log (m+n)),其中 m 和 n 分别是两个数组的长度。
- 空间复杂度: O(log (m+n)),用于递归调用栈。
6. 总结与扩展
这道题是一道非常经典的题目,它考察了对二分查找的理解和应用。 通过这道题,可以掌握如何使用二分查找来解决一些特定的问题,例如寻找两个有序数组的中位数。
扩展:
- 理解二分查找的原理和应用场景。
- 尝试解决类似的二分查找问题,例如:
- 力扣 34 题:在排序数组中查找元素的第一个和最后一个位置
- 力扣 153 题:寻找旋转排序数组中的最小值
希望这篇博客能够帮助你理解力扣 4 题“寻找两个正序数组的中位数”的解法,并对二分查找有一个更深入的认识。 祝你刷题愉快!
力扣题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)),满足题目要求。
- 总结
本题是一个典型的使用二分查找解决问题的例子。通过在两个有序数组中找到合适的分割点,我们可以计算出中位数。Java和JavaScript的解法思路是一致的,只是在语法和一些细节上有所不同。掌握二分查找的基本思想对于解决类似的问题非常重要。
力扣 4. 寻找两个正序数组的中位数:新手入门详解
一、题目概述
题目描述
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。需要找出并返回这两个正序数组的中位数,且算法的时间复杂度应该为
O
(
l
o
g
(
m
+
n
)
)
O(log (m + n))
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
。
- 输入:
提示
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 ( m + n ) O(m + n) O(m+n),因为需要遍历两个数组。
- 空间复杂度: O ( m + n ) O(m + n) O(m+n),需要创建一个新数组来存储合并后的结果。
三、二分查找法思路
为了满足时间复杂度 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]
。 - 如果满足条件,根据数组总长度的奇偶性计算中位数;如果不满足条件,调整二分查找的范围。
复杂度分析
- 时间复杂度: O ( l o g ( m i n ( m , n ) ) ) O(log(min(m, n))) O(log(min(m,n))),因为只对较短的数组进行二分查找。
- 空间复杂度: O ( 1 ) O(1) O(1),只使用了常数级的额外空间。
四、Java 代码实现
简单合并排序法
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.");
}
}
五、JavaScript 代码实现
简单合并排序法
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)) 的时间复杂度要求。对于新手来说,理解二分查找法的思路和实现是关键。希望通过这篇博客,你能掌握本题的解题思路和代码实现。
4. 寻找两个正序数组的中位数:Java与JavaScript实现详解
目录
- 引言
- 问题分析
- Java实现
3.1 方法概述
3.2 代码实现 - JavaScript实现
4.1 方法概述
4.2 代码实现 - 总结
- 参考资料
1. 引言
在力扣(LeetCode)的题目中,4. 寻找两个正序数组的中位数是一个中等难度的算法问题。题目要求我们找出两个已排序数组合并后的中位数。由于两个数组的长度可能不同,我们需要一种高效的方法来解决这个问题,其时间复杂度应为O(log(m+n))。
2. 问题分析
为了找到两个数组合并后的中位数,我们可以考虑以下策略:
- 直接合并两个数组,然后找到中位数。这种方法的时间复杂度为O(m+n),不符合题目要求。
- 使用二分查找的方法,在其中一个数组中找到中位数所在的位置,然后根据这个位置在另一个数组中找到对应的元素,从而确定中位数。这种方法的时间复杂度为O(log(min(m, n))),符合题目要求。
3. Java实现
3.1 方法概述
我们可以使用二分查找的方法来解决这个问题。具体步骤如下:
- 确定两个数组的长度。
- 使用二分查找在较短的数组中找到中位数的位置。
- 根据找到的位置,确定另一个数组中对应的位置。
- 计算中位数。
3.2 代码实现
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;
}
4. JavaScript实现
4.1 方法概述
JavaScript的实现与Java类似,也是使用二分查找的方法来解决这个问题。
4.2 代码实现
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;
}
5. 总结
本文介绍了如何使用Java和JavaScript解决寻找两个正序数组的中位数问题。通过使用二分查找的方法,我们可以有效地找到两个数组合并后的中位数,且时间复杂度为O(log(min(m, n)))。
6. 参考资料
力扣4. 寻找两个正序数组的中位数 - 二分查找详解
目录
问题描述
给定两个大小分别为 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小的元素
- 确保nums1是较短的数组:减少二分查找的范围
- 二分查找分割点:
- 在nums1中找分割点i,nums2中分割点j=(m+n+1)/2-i
- 确保nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]
- 根据数组长度奇偶性返回结果:
- 奇数:maxLeft
- 偶数:(maxLeft + minRight)/2
JavaScript解法
/**
* @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;
}
};
Java解法
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;
}
}
}
复杂度分析
- 时间复杂度:O(log(min(m,n))) - 我们只在较短的数组上进行二分查找
- 空间复杂度:O(1) - 只使用了常数级别的额外空间
算法图解
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
常见误区
- 没有处理数组为空的情况:需要单独处理一个数组为空的情况
- 边界条件处理不当:当分割点在数组边界时,需要特殊处理
- 整数除法问题:在Java中要注意整数除法会截断小数部分
- 没有确保nums1是较短的数组:这会影响算法效率
应用场景
- 大数据流的中位数计算
- 分布式系统中的数据统计
- 数据库查询优化
- 实时数据分析系统
总结
- 二分查找是解决此类有序数组问题的有效方法
- 关键点在于将中位数问题转化为寻找第k小元素问题
- 边界处理是算法正确性的重要保障
- 时间复杂度优化的关键在于只在较短的数组上进行二分
掌握这种二分查找的变种应用,能够解决许多类似的查找问题。建议通过画图来理解分割点的选择过程,并尝试解决类似的题目来巩固这一知识点。
寻找两个有序数组的中位数:Java与JavaScript实现详解
目录
问题描述
给定两个大小分别为 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),仍不满足题目要求
方法三:二分查找法(最优解)
- 确保nums1是较短的数组
- 在nums1中进行二分查找,确定分割线位置
- 根据分割线位置,检查是否满足中位数条件
- 调整二分查找范围,直到找到正确分割线
优点:时间复杂度为O(log(min(m,n))),满足题目要求
Java实现
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");
}
}
JavaScript实现
/**
* @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");
};
复杂度分析
- 时间复杂度:O(log(min(m,n))),因为我们只在较短的数组上进行二分查找
- 空间复杂度:O(1),只使用了常数级别的额外空间
边界条件处理
- 一个数组为空:直接返回另一个数组的中位数
- 分割线在数组边界:使用正/负无穷大处理边界值
- 数组总长度为奇数/偶数:分别处理中位数计算方式
- 所有元素相同:算法仍然适用
- 交叉数组:如[1,3]和[2,4],算法能正确处理
实际应用场景
- 数据库查询优化:合并两个有序结果集并快速找到中值
- 实时数据处理系统:处理来自多个源的有序数据流
- 统计分析:快速计算大规模数据集的中位数
- 机器学习:特征工程中处理有序特征的分位数
总结与思考
寻找两个有序数组中位数的问题展示了如何将经典二分查找算法应用于更复杂的问题场景。关键点在于:
- 问题转化:将中位数问题转化为寻找两个数组的分割线问题
- 边界处理:正确处理分割线在数组边界的情况
- 算法选择:利用二分查找实现对数级别的时间复杂度
Java和JavaScript的实现非常相似,主要区别在于:
- Java有严格的类型系统,JavaScript是动态类型
- Java使用
Integer.MIN_VALUE/MAX_VALUE
,JavaScript使用Infinity
- Java需要显式类型转换,JavaScript自动处理数字类型
对于新手来说,建议:
- 先理解简单解法(合并数组法)
- 然后尝试双指针法
- 最后再挑战二分查找法
- 通过具体例子手动模拟算法执行过程
这种二分查找的变种思想可以应用于许多需要高效搜索的问题,如寻找第K小的元素、矩阵搜索等。
二分查找解决两个有序数组的中位数问题——算法详解与代码实现
目录
- 问题描述与理解
- 暴力解法与不足
- 二分查找的核心思想
- 算法步骤与细节解析
- 代码实现(JavaScript和Java)
- 复杂度分析与总结
1. 问题描述与理解
题目:给定两个大小分别为 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)),因此不能直接合并数组。
2. 暴力解法与不足
暴力解法思路
- 将两个有序数组合并成一个有序数组。
- 根据合并后的数组长度计算中位数。
合并数组的实现:
- 使用双指针逐个比较元素,合并成一个新数组。
- 时间复杂度为 O(m + n),无法满足题目要求。
问题:
- 当数组长度较大时(如
m
或n
接近1000
),合并数组会消耗大量时间和空间。
3. 二分查找的核心思想
核心思路
中位数的定义:
- 若总长度为奇数,则中位数是第
(m + n + 1) / 2
个元素。 - 若总长度为偶数,则中位数是第
(m + n) / 2
和第(m + n) / 2 + 1
个元素的平均值。
关键问题:如何在 O(log(m + n)) 时间复杂度下找到第 k
小的元素?
二分查找策略:
- 分治思想:通过比较两个数组的中间元素,排除不可能包含第
k
小元素的部分。 - 递归实现:每次将问题规模缩小一半,直到找到目标元素。
4. 算法步骤与细节解析
算法步骤
- 确定总长度:计算两个数组的总长度
total = m + n
。 - 判断奇偶性:根据
total
决定需要找的第k
小元素的位置。 - 二分查找第k小元素:
- 在较短的数组中进行二分查找,以减少递归次数。
- 比较两个数组的中间元素,排除较小的部分。
- 递归终止条件:当其中一个数组为空或
k
为1
时,直接取最小值。
关键细节
- 选择较短的数组:始终在较短的数组上进行二分,以确保时间复杂度为 O(log(min(m, n)))。
- 递归终止条件:
- 当一个数组为空时,直接从另一个数组中取第
k
小元素。 - 当
k = 1
时,取两个数组当前起始位置的较小值。
- 当一个数组为空时,直接从另一个数组中取第
- 元素比较:通过比较
nums1[i - 1]
和nums2[j - 1]
,确定哪一部分可以被排除。
5. 代码实现
JavaScript 实现
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;
};
Java 实现
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;
}
}
6. 复杂度分析与总结
复杂度分析
- 时间复杂度:O(log(min(m, n)))
- 二分查找的范围是较短数组的长度,每次递归或循环将问题规模减半。
- 空间复杂度:O(1)
- 仅使用常数级别的额外空间。
总结
- 核心思想:通过二分查找找到分割点,使得两个数组的左半部分的最大值小于右半部分的最小值。
- 关键技巧:
- 确保分割点满足
i + j = (m + n + 1) / 2
(奇数情况)或i + j = (m + n) / 2
(偶数情况)。 - 通过调整分割点
i
的位置,逐步逼近中位数。
- 确保分割点满足
- 适用场景:当需要在两个有序数组中高效查找中位数或第
k
小元素时,此方法非常有效。
通过本题,可以掌握二分查找在处理有序数组问题中的高级应用,理解如何通过分治思想优化算法的时间复杂度。
力扣4. 寻找两个正序数组的中位数:二分查找终极指南(Java/JavaScript实现)
目录
- 问题描述
- 暴力解法分析
- 二分查找核心思路
- 算法步骤详解
- Java代码实现(递归+迭代双解)
- JavaScript代码实现
- 复杂度证明
- 边界条件处理
- 总结
1. 问题描述
给定两个有序数组 nums1
和 nums2
,长度分别为 m
和 n
,要求时间复杂度 O(log(m+n))
找到合并后的中位数。
示例:
输入:nums1 = [1,3], nums2 = [2]
→ 输出:2.0
输入:nums1 = [1,2], nums2 = [3,4]
→ 输出:2.5
2. 暴力解法分析
合并数组后取中位数的时间复杂度为 O(m+n)
,无法满足题目要求。我们需要更高效的分治策略。
3. 二分查找核心思路
将问题转化为寻找第k小元素,通过每次排除 k/2
个元素实现对数级时间复杂度。
关键概念
- 分割线位置:
在数组中找到分割线,使得左侧元素均 ≤ 右侧元素。 - 中位数条件:
分割线左侧元素数量等于总元素数一半(偶数)或多一个(奇数)。
4. 算法步骤详解
步骤一:处理奇偶统一
无论总长度奇偶,都寻找两个分割点:
- 奇数:
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]
步骤三:处理边界情况
- 分割线在数组最左:左侧无元素
- 分割线在数组最右:右侧无元素
5. Java代码实现(递归+迭代双解)
递归解法
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;
}
}
6. JavaScript代码实现
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;
}
7. 复杂度证明
- 时间复杂度:O(log(min(m,n)))
每次循环排除一半元素,最多执行 log(min(m,n)) 次 - 空间复杂度:O(1)
仅使用常数级额外空间
8. 边界条件处理
场景 | 处理方案 |
---|---|
空数组 | 直接返回另一个数组的中位数 |
全左分割 | 取另一数组的右侧元素 |
全右分割 | 取另一数组的左侧元素 |
元素交叉 | 比较相邻元素确定正确分割线 |
9. 总结
- 核心思想:将中位数问题转化为寻找分割线,利用二分查找快速定位
- 实现要点:
- 始终保持
nums1
为较短数组 - 正确处理分割线的边界值
- 巧用正负无穷处理边界条件
- 始终保持
- 扩展应用:该方法可推广到寻找任意第k小元素的场景
通过这种分治策略,我们成功将时间复杂度降低到对数级别,完美满足题目要求。理解这个算法需要多画图模拟分割线移动过程,建议通过示例数组手动走一遍算法流程加深理解。
LeetCode 4. 寻找两个正序数组的中位数:Java与JavaScript双解法详解
目录
- 问题描述
- 核心思路:二分法查找第k小元素
- 算法思路
- Java实现
- 代码实现
- 代码解析
- JavaScript实现
- 代码实现
- 代码解析
- 复杂度分析
- 总结
- 参考资料
1. 问题描述
给定两个正序(升序)数组 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)
2. 核心思路:二分法查找第k小元素
将问题转化为寻找两个数组中第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
。
3. Java实现
代码实现
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
防止越界,确保排除的元素个数正确。
4. JavaScript实现
代码实现
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;
}
}
}
代码解析
- 逻辑与Java一致:通过循环代替递归,避免栈溢出。
- 边界处理:使用
Math.min
确保索引不越界。
5. 复杂度分析
- 时间复杂度:O(log(m+n)),每次排除约一半元素。
- 空间复杂度:O(1),仅用常数级变量。
6. 总结
- 核心技巧:将问题转化为寻找第k小元素,通过二分法快速缩小范围。
- 边界处理:特别注意数组越界和剩余元素不足的情况。
- 适用场景:高效处理有序数组的合并、中位数查找问题。
7. 参考资料
LeetCode题解:二分法思路与实现
二分查找法详细推导步骤
Java与JavaScript代码实现对比