题目链接: LeetCode|4.寻找两个正序数组的中位数
题面解释
找出两个有序数组合并后的中位数. 但要求时间复杂度\(\mathit{O}(log(m + n))\).
解法一 二分
笔者看到复杂度\(\mathit{O}(log(m + n))\)第一想法便是二分, 但怎么二分? 两个数组只是内部有序, 两个数组并不有序, 常规对数组二分的思路肯定是不行的.
那该如何进行二分呢, 对答案进行二分. 令k为所寻找的k阶数, 初始为中位数, 则此时我们取两个数组的前\(k/2\)部分出来, 比较\(nums1[k/2]\)和\(nums2[k/2]\), 则可以直接排除\(k/2\)个数. 我们以下图例子来解释.

\(m = n = 4\), 我们有 \(k = 4, k/2 = 2\), 于是我们取两个数组的前两位, 比较3和5, 不难看出, 1和3不可能是中位数. 为什么? 因为1和3的阶必定小于\(k = 4\), 即便第一个数组的3比第二个数组的5前面所有数都大, 3的阶也只能是3, 1就更不必说了. 而5前面的数可能大于3后面的数(例子中未能体现, 读者可以自行构造一个满足这种情况的简单例子). 因此通过这种操作, 我们可以排掉 \(k/2\) 个不可能是中位数的元素, 接着我们令\(k = k - k/2\), 将被排除的数去掉, 重新进行上述过程.

选取两个数组的前1位.比较2 和 4, 可以排除掉2. 此时过程进行到了边界处, 接下来\(k = 1\)无法继续了. 而此时剩下的4和5正是我们需要的中位数.
Tips: 为什么第二个数组不是选择2, 5, 7?
因为我们在上面的步骤中无法确定2和5是中位数与否, 此时的有效信息不足. 而把2 5 7都放进来与4进行比较会出现bug, 以上例, 4会被错误的排除掉
k为奇数时情况稍有不同, 但大多是一些细节问题, 笔者在此不再赘述. 推荐读者自己构建一个例子去推算上面的过程, 相信不难理解其中要义.
每次排除\(k/2\), 而 \(k = (m + n + 1) / 2\), 故时间复杂度\(\mathit{O}(log(m + n))\).
参考代码如下:
1 | class Solution { |
Tips: 本题思路并不难想出或者说并不难理解, 但非常考验代码功底, 对细节和边界情况的考察更像是此题的侧重点. 还望看到此处的读者能静下心来调试代码, 祝早日AC.
解法二 分割
笔者阅读题解时注意到的巧妙的解法, 原链接在此P4|windliang
更详细的数学证明, 思路和代码请阅读原文, 笔者在此只给出自己对其的理解.
本质来说, 两种方法殊途同归, 但分割的方法较为巧妙一些. 中位数, 或者说k阶数, 就是将数组分为两个部分, 其一是比k阶数小的部分, 其二是比k阶数大的部分.
对于有序的一个数组, 其性质本身就已经满足, 我们不妨将两部分简称为左半部分(小)和右半部分(大). 对于两个有序数组, 情况是一样的, 如果存在一种划分, 满足左边最大MaxLeft小于右边最小MinRight, 我们则认为这是关于k阶数的一种有效划分.
因此, 这道题就被转化为寻找数组中满足中位数的有效划分, 关于特定k阶数划分, 还需要满足左半部分和右半部分的数量关系, 若规定将k阶数划在左半部分, 则左半部分有k个元素, 右半部分有 n - k 个元素.
后面的事情就简单了, 固定数组1的划分位置, 根据数量关系找到数组2待判定划分位置, 根据MaxLeft和MinRight的大小关系决定数组1划分位置的左移或右移, 该过程可以通过二分完成.