1. 引言:挑战 LeetCode 经典算法题
在算法这片广袤无垠的天地里,一道道经典题目宛如夜空中熠熠生辉的星辰,持续吸引着开发者们投身其中,不断探索。今天,我们继续将目光聚焦于 LeetCode 平台上一道极具代表性的题目:如何运用 Java 语言,精确找出两个正序数组的中位数,并且要将算法的时间复杂度严格控制在 (O(log (m + n))) 。这一挑战过程就像一场充满惊险与刺激的冒险,其间思维的火花不断碰撞,探索的乐趣贯穿始终,让我们一同开启这段精彩绝伦的奇妙之旅。
2. 问题拆解:正序数组中位数探寻任务
题目明确给出两个大小分别为 m 和 n 的正序数组 nums1 与 nums2 。我们的核心任务,便是毫无差错地找出这两个数组合并之后的中位数。形象地来讲,这就如同在一堆已经依照顺序排列整齐的拼图碎片里,精准定位到处于最中间位置的那一块(若总碎片数为偶数,则是中间两块的平均值),这个中位数对于把握整个数据集合的中心趋势起着至关重要的作用。
2.1 示例解析:直观理解中位数计算
-
示例一:当输入为 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 + 3) / 2 = 2.5 。这一计算过程,就像是在数字队列中精准地找到了中间的平衡点,巧妙且清晰地反映出数据的集中趋势。
3. 解题思路:巧用二分查找破题
要想实现 (O(log (m + n))) 这般高效的时间复杂度,二分查找这一强大有力的工具无疑是我们的 “秘密武器”。不妨想象一下,你身处一座规模宏大、藏书海量的图书馆,而你需要寻找一本特定的书籍。二分查找就如同赋予了你一种神奇的能力,每一次操作都能够精准地将搜索范围缩小一半,从而迅速锁定目标,极大程度地提升查找效率。
具体到本题当中,我们的解题思路是在较短的那个数组上巧妙施展二分查找的精妙技巧。我们把两个数组视作两块大蛋糕,需要将它们切成左右两部分。在切分的时候,有两个关键要求:其一,左右两部分的元素个数要尽可能相等(若总元素个数为奇数,那就让左边比右边多一个);其二,左边部分的所有元素都必须小于等于右边部分的所有元素。一旦成功完成这样的切分操作,处于中间位置的元素(若总元素个数为偶数,则是中间两个元素的平均值),便是我们梦寐以求的中位数。
4. Java 代码呈现:核心实现逻辑
public class MedianOfTwoSortedArrays {
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 imin = 0, imax = m, halfLen = (m + n + 1) / 2;
while (imin <= imax) {
// 在nums1上进行二分查找
int i = (imin + imax) / 2;
// 根据i计算nums2上的划分位置
int j = halfLen - i;
if (i < m && nums2[j - 1] > nums1[i]) {
// i太小,需要增大
imin = i + 1;
} else if (i > 0 && nums1[i - 1] > nums2[j]) {
// i太大,需要减小
imax = i - 1;
} else {
// i是完美的划分位置
int maxOfLeft;
if (i == 0) {
// nums1左半部分为空
maxOfLeft = nums2[j - 1];
} else if (j == 0) {
// nums2左半部分为空
maxOfLeft = nums1[i - 1];
} else {
// 取两个数组左半部分的最大值
maxOfLeft = Math.max(nums1[i - 1], nums2[j - 1]);
}
if ((m + n) % 2 == 1) {
// 总元素个数为奇数,中位数就是左半部分的最大值
return maxOfLeft;
}
int minOfRight;
if (i == m) {
// nums1右半部分为空
minOfRight = nums2[j];
} else if (j == n) {
// nums2右半部分为空
minOfRight = nums1[i];
} else {
// 取两个数组右半部分的最小值
minOfRight = Math.min(nums1[i], nums2[j]);
}
// 总元素个数为偶数,中位数是左半部分最大值和右半部分最小值的平均值
return (maxOfLeft + minOfRight) / 2.0;
}
}
// 不会执行到这里
throw new IllegalArgumentException();
}
public static void main(String[] args) {
MedianOfTwoSortedArrays solution = new MedianOfTwoSortedArrays();
int[] nums1 = {1, 3};
int[] nums2 = {2};
System.out.println(solution.findMedianSortedArrays(nums1, nums2));
nums1 = new int[]{1, 2};
nums2 = new int[]{3, 4};
System.out.println(solution.findMedianSortedArrays(nums1, nums2));
}
}
5. 代码逐行剖析:理解每一步操作
5.1 数组长度调整策略
代码一开始,就会细致地比较 nums1 和 nums2 的长度。一旦发现 nums1 的长度大于 nums2 ,便会灵活巧妙地调用自身方法,将两个数组的位置进行互换,以此确保 nums1 始终是较短的那个数组。这一操作就如同在整理书架时,特意把书本数量较少的那一排放在最便于拿取的位置,为后续进行二分查找提供了极大的便利,能够显著缩小查找范围,进而有效提升算法的整体效率。
5.2 二分查找初始化与推进
初始化了多个关键变量,其中 imin 和 imax 就如同两个忠诚的 “哨兵”,精准无误地标记出二分查找的范围边界;而 halfLen 则像是一把特制的 “尺子”,专门用于确定数组划分位置的关键数值。在 while 循环中,通过 (imin + imax) / 2 这一精巧的运算,在 nums1 数组上成功确定划分位置 i 。紧接着,依据 halfLen 迅速计算出 nums2 数组对应的划分位置 j 。这一过程,就仿佛是在两块拼图板上同时精准地寻找分割线,使得左右两边的拼图数量恰到好处,为后续正确划分数组奠定了坚实的基础。
5.3 划分位置动态优化
当 i 取值过小时,就会出现 nums2 左半部分的某个元素大于 nums1 右半部分对应元素的情况。此时,程序会毫不犹豫地将 imin 增大,促使 i 向右移动,这就如同在拼图过程中发现某块碎片位置摆放错误,及时将其调整到正确方向。反之,若 i 取值过大,导致 nums1 左半部分的某个元素大于 nums2 右半部分对应元素,程序则会减小 imax ,让 i 向左移动,通过不断地这样优化划分位置,直至达到理想状态。
5.4 中位数计算与输出
一旦成功找到完美的划分位置 i ,便正式进入计算中位数的关键环节。首先确定左半部分的最大值 maxOfLeft :若 nums1 左半部分为空,那就直接选取 nums2 左半部分的最后一个元素;若 nums2 左半部分为空,则选取 nums1 左半部分的最后一个元素;若两者均不为空,就从两个数组左半部分中选取较大值。若总元素个数为奇数,那么 maxOfLeft 就是我们苦苦追寻的中位数,直接返回该结果,顺利完成任务。若总元素个数为偶数,还需确定右半部分的最小值 minOfRight ,其计算方式与确定 maxOfLeft 类似。最后,通过 (maxOfLeft + minOfRight) / 2.0 这一公式,精准计算出中位数,这就如同找到了拼图中间的关键连接点,将左右两边完美融合,输出最终答案。
6. 复杂度分析:算法效率评估
6.1 时间复杂度
(O(log(min(m, n)))) ,这是因为我们巧妙地将二分查找操作限定在较短的数组上。每次执行二分查找,都如同在迷宫中找到了正确的岔路,能够将搜索范围精准地缩小一半,极大地提高了查找效率,使得算法能够快速朝着目标前进。
6.2 空间复杂度
(O(1)) ,整个算法在执行过程中,仅仅使用了几个固定不变的变量,就如同在一个小巧而精致的工作室里工作,无需占用过多额外空间,充分展现了算法设计的简洁高效,尽显精妙之处。
感谢各位的阅读,后续将持续给大家讲解力扣中的算法题,如果觉得这篇内容对你有帮助,别忘了点赞和关注,后续还有更多精彩的算法解析与你分享!