LeetCode——4. Median of Two Sorted Arrays
一.题目链接:https://leetcode.com/problems/median-of-two-sorted-arrays
二.题目大意:
给定两个排序过的数组,求出两个数组的中位数,要求时间复杂度为O(log(m+n)).
三.题解:
这是我在LeetCode上做的第一道hard级别的题目,没想到难度系数达到了5!被虐哭了。。。言归正传,这道题目的解题思路看起来并不难,难点在于它限定了时间复杂度,而且是对数级别的复杂度(说明优化程度已经相当高了)。这里,我给出三种解决方案,当然前两种时间复杂度并不满足要求(仅供参考思路);只有第三种方案符合题目要求:
方法1:
看到这种题目,首先想到的就是对两个数组进行合并,合并成一个有序的数组,然后根据数组元素个数的奇偶性求中位数即可。此处的排序方法有多种,最适合该题目的排序算法应该是归并排序。对于归并排序(默认升序),只需定义2个指针pa和pb分别指向数组a(长度为m)和数组b(长度为n),如果数组a的当前元素小,则*pa赋值给新数组,并且pa++;否则,*pb赋值给新数组,并且pb++;这么遍历指导遍历完某个数组,剩下的部分直接拼接到新数组末尾。最后找中位数。这么做的话时间复杂度为O(m+n),空间复杂度为O(1)。
方法2:
我们想要的仅仅是数组的中位数,而排序操作实际上消耗的代价太大,而我们没必要消耗如此大的代价。所以,此处不必对数组进行合并与排序,但还是借鉴方法1的思想,定义一个计数器cnt,每当*pa<*pb时,cnt++,pa++;每当*pa>=*pb,cnt++,pb++;当cnt==k时们就能找到相当于合并后数组的第k大的数了(此处包括本文所指的第k大的数指的是在数组中位置排序为k的数,而不是按大小来算的)。而中位数就是去中间大的那个数。这么做的话,时间复杂度也是O(m+n),空间复杂度O(1)。但是平均时间复杂度肯定是比方法1要小的。所以,方法2效率更好一些;遇到类似题目时,应该优先考虑方法2。
方法3:
前两种方法,都不能满足题目的要求。到底怎样才能找到指数级别的方法呢?首先我们来求一个更一般化的问题:求有序数组中第k个元素。而求有序数组的中位数实际就是这个问题的一个特例。具体一点说,假设数组的长度为n,当n为奇数时,即求第(n/2+1)个元素,当n为偶数时,即求第(n/2+1)和第(n/2)个元素,然后取他们的均值。下面我们从两个角度来分析:
1.如何搜索两个有序数组中第k个元素呢,这里又有个技巧。假设数组都是从小到大排列,对于第一个数组中前p个元素和第二个数组中前q个元素,我们想要的最终结果是:(1)p+q等于k,(2)第一个数组中第p个元素(即A[p-1])和第二个数组中第q-1个元素(即B[q-2])都小于等于总数组第k个元素(或者第一个数组中第p-1个元素(即A[p-2])和第二个数组中列q个元素(即B[q-q]))。因为总数组中,必然有k-1个元素小于等于第k个元素。这样第p个元素或者第q个元素就是我们要找的第k个元素。
2.如果我们每次都能够删除一个一定在k位置之前的元素,那么我们需要进行k次。但是如果我们每次都删除一半呢?(即p和q都是k/2级别的)要达到log级别,我们应该充分利用数组的有序性,也就是类似于二分查找。
结合角度1和角度2,我们可以通过二分法将问题规模缩小,假设p=k/2,则q=k-p=k/2,且p+q=k。这样A[0]~A[p-1]共有k/2个元素,如果第一个数组第p个元素小于第二个数组第q个元素(即A[k/2-1] < B[k/2-1]),我们不确定第二个数组中第q个元素是大了还是小了(即B[k/2-1]的大小不确定),但第一个数组的前p个元素肯定都小于目标(反证法可证),所以我们将第一个序列前p个元素全部抛弃(A[0]~A[k/2 -1]全部抛弃),形成一个较短的新数组(从A[k/2]开始)。然后,用新数组替代原先的第一个数组,再找其中的第k-p个元素(因为我们已经排除了p个元素,k需要更新为k-p,相当于总数组的第k个位置变成了总数组第k-p个位置(总数组变小了)),依次递归。同理,如果第一个数组中的第p个元素大于第二个数组中的第q个元素,我们则抛弃第二个数组的前q个元素(B[0]~B[k/2 -1]全部抛弃)。递归的终止条件有如下几种:
(1)较短数组中的所有元素都被抛弃,则返回较长数组中的第k个元素(在数组中下标是k-1)。
(2)第一个数组中的第p个元素等于二数组中第q个元素(即A[k/2-1] == B[k/2 - 1]),此时第一个数组中的第p个元素和二数组中第q个元素就是总数组中的第k个元素。(此时返回A[k/2-1]或者B[k/2-1]都可以)
(3)k == 1时,此时k不能再继续更新了,返回min(A[0],B[0])。(即本次传参时的数组A、B的起始位置)、
此外,还要注意:每次递归不仅要更新数组起始位置(起始位置之前的元素被抛弃),也要更新k的大小(扣除被抛弃的元素)!代码如下:
class Solution {
public:
double findMedianSortedArrays(const vector<int>& A, const vector<int>& B) {
const int m = A.size();
const int n = B.size();
int total = m + n;
if (total & 1)//判断数组长度是否为奇数
return find_kth(A.begin(), m, B.begin(), n, total / 2 + 1);
else
return (find_kth(A.begin(), m, B.begin(), n, total / 2)+ find_kth(A.begin(), m, B.begin(), n, total / 2 + 1)) / 2.0;
}
private:
static int find_kth(std::vector<int>::const_iterator A, int m,std::vector<int>::const_iterator B, int n, int k) {
//始终保证m小于等于n
if (m > n)
return find_kth(B, n, A, m, k);//此处一定不要忘记加return
if (m == 0)
return *(B + k - 1);
if (k == 1)
return min(*A, *B);
//相当于把k分成p和q两部分
int ia = min(k / 2, m), ib = k - ia;
if (*(A + ia - 1) < *(B + ib - 1))
return find_kth(A + ia, m - ia, B, n, k - ia);
else if (*(A + ia - 1) > *(B + ib - 1))
return find_kth(A, m, B + ib, n - ib, k - ib);
else
return A[ia - 1];
}
};
该方法的时间复杂度为O(log(m+n)),空间复杂度为O(log(m+n))(递归过程中每次调用都为O(1),总共为O(log(m+n))),符合题目要求。
注意:
1.当数组A的长度小于k/2时,此时选择p=m;那么此时B的长度一定是大于k/2的,并且舍去的一定是B的k/2个元素。此处利用反证法证明:
假设第K位置的元素在B的前k/2中,例如位置在索引i(i <= k/2-1)那么A必然拥有前k中的k -(i+1)个元素,而i <= k/2-1,则 i+1 <= k/2 , k - (i+1) >= k/2与条件:A的元素不够k/2矛盾,所以假设不成立,得证。
所以此时舍去的必然是B的k/2个元素,下一步迭代的过程中k也会变小所以对于更新后的k,A的长度还是小于k/2的话,则会继续删除B的k/2个元素。
2.总的来说,删除元素的过程主要包括三种类型:
(1).A[k/2-1] < B[k/2-1],此时删除B的前k/2个元素。
(2)B[k/2-1] < B[k/2-1],此时删除A的前k/2个元素。
(3)A的元素个数小于k/2的话,此时删除B的前k/2个元素。 (不过这一过程并没有在程序中显试的表现出来,而是隐含在程序处理的过程中,此处说明这一步只是为了更好的理解本算法;实际上
int ia = min(k / 2, m)
这行代码就是这一点的体现。)
3.函数find_kth中的参数m、n和k,都表示的是数字的位置,而不是数组的下标;实际数组的下标要再减去1。
=====================================================附录====================================================================
1.A[k/2-1]<B[k/2-1]时,总数组第k位置的数,会有三种情况:
(1)在A[k/2]~A[m-1]这一部分中。例如:
A={2,3,5}
B={1,7,8}
找到第4位置上的数,此时为5。
(2)在B[0]~B[k/2-1]这一部分。例如:
A={1,2,3,8}
B={,5,7}
找到第4位置上的数,此时为4。
(3)就是B[k/2-1]。例如:
A={1,2,5}
B={3,4,7}
找到第4位置上的数,此时为4。
2.当A[k/2-1]>B[k/2`-1]时,也一样。
3.为什么当A[k/2-1] == B[k/2-1]时,A[k/2-1]或B[k/2-1]就是第k位置上的数?
证明:
对于A[0]~A[k/2-2],它们全部都小于等于A[k/2-1],所以它们一定是在总数组中第k位置之前。同理,对于B[0]~B[k/2-2],它们全部都小于等于B[k/2-1],所以它们也一定是在总数组中第k位置之前。这样就有(k/2-1)+(k/2-1) = k-2个已确定的数在k位置之前。又由于A[k/2-1] == B[k/2-1],而A[k/2-1]或者B[k/2-1]是大于这k-2个数中的最小的数,故第k位置的数一定是A[k/2-1]或者B[k/2-1]。
LeetCode——4. Median of Two Sorted Arrays的更多相关文章
- 【算法之美】求解两个有序数组的中位数 — leetcode 4. Median of Two Sorted Arrays
一道非常经典的题目,Median of Two Sorted Arrays.(PS:leetcode 我已经做了 190 道,欢迎围观全部题解 https://github.com/hanzichi/ ...
- LeetCode(3) || Median of Two Sorted Arrays
LeetCode(3) || Median of Two Sorted Arrays 题记 之前做了3题,感觉难度一般,没想到突然来了这道比较难的,星期六花了一天的时间才做完,可见以前基础太差了. 题 ...
- LeetCode 4 Median of Two Sorted Arrays (两个数组的mid值)
题目来源:https://leetcode.com/problems/median-of-two-sorted-arrays/ There are two sorted arrays nums1 an ...
- Leetcode 4. Median of Two Sorted Arrays(二分)
4. Median of Two Sorted Arrays 题目链接:https://leetcode.com/problems/median-of-two-sorted-arrays/ Descr ...
- LeetCode 4. Median of Two Sorted Arrays & 归并排序
Median of Two Sorted Arrays 搜索时间复杂度的时候,看到归并排序比较适合这个题目.中位数直接取即可,所以重点是排序. 再来看看治阶段,我们需要将两个已经有序的子序列合并成一个 ...
- 第三周 Leetcode 4. Median of Two Sorted Arrays (HARD)
4. Median of Two Sorted Arrays 给定两个有序的整数序列.求中位数,要求复杂度为对数级别. 通常的思路,我们二分搜索中位数,对某个序列里的某个数 我们可以在对数时间内通过二 ...
- Leetcode 4. Median of Two Sorted Arrays(中位数+二分答案+递归)
4. Median of Two Sorted Arrays Hard There are two sorted arrays nums1 and nums2 of size m and n resp ...
- LeetCode 004 Median of Two Sorted Arrays
题目描述:Median of Two Sorted Arrays There are two sorted arrays A and B of size m and n respectively. F ...
- leetcode 4. Median of Two Sorted Arrays
https://leetcode.com/problems/median-of-two-sorted-arrays/ There are two sorted arrays nums1 and num ...
- leetcode之 median of two sorted arrays
这是我做的第二个leetcode题目,一开始以为和第一个一样很简单,但是做的过程中才发现这个题目非常难,给人一种“刚上战场就踩上地雷挂掉了”的感觉.后来搜了一下leetcode的难度分布表(leetc ...
随机推荐
- GitHub使用教程、注册与安装
GitHub注册与安装 本文提供全流程,中文翻译.Chinar坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请调整网页缩放比例至200%) 1 进入GitHub官网:http ...
- day 014 python 内置函数
1. lamda匿名函数2. sorted()3. filter()4. map()5. 递归函数 一 匿名函数(lambda) 函数名= lambda 参数: 返回值 简单算法 a+b 常规算 ...
- Project Euler 54
#include<bits/stdc++.h> using namespace std; ]; ]; ; map<char,int> mp; //map<char,cha ...
- Red Hat OpenShift
Core Concepts 基本概念 Master Master监控其他Node和Pod 提供Web Console Node Node是k8s的工作机器 Node中运行Pod,pod可以在node中 ...
- maven settings.xml配置优化
<?xml version="1.0" encoding="UTF-8"?> <settings> <localRepositor ...
- 【mysql】索引原理-MySQL索引原理以及查询优化
转载:https://www.cnblogs.com/bypp/p/7755307.html 一.介绍 1.什么是索引? 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性 ...
- linux之数据备份
第一种方法:tar备份 [root@bogon ~]# cat bp/linux.txt no centos [root@bogon ~]# tar cvf bp.tar bp //打包bp目录 bp ...
- Creating Node.js Command Line Utilities to Improve Your Workflow
转自:https://developer.telerik.com/featured/creating-node-js-command-line-utilities-improve-workflow/ ...
- Debug outlook add-in (office.js) 小技巧
这几天在使用office.js 做outlook add-in的时候出现了一个问题: 不知道运行时去调试. 这里给大家介绍两个调试add-in 的方法. office365 其他软件 add-ins ...
- Autofac学习之三种生命周期:InstancePerLifetimeScope、SingleInstance、InstancePerDependency 【转载】
InstancePerLifetimeScope:同一个Lifetime生成的对象是同一个实例 SingleInstance:单例模式,每次调用,都会使用同一个实例化的对象:每次都用同一个对象: In ...