### 4. Median of Two Sorted Arrays

题目:
<https://leetcode.com/problems/median-of-two-sorted-arrays/>


难度:

Hard


一看到的时候，觉得跟CLRS书上的一道习题类似
求X[1....n] Y[1....n] 的 median

习题 9.3-8


Let X[1..n] and Y [1..n] be two arrays, each containing n numbers already in sorted order. Give an O(lg n)-time algorithn to find the median of all 2n elements in arrays X and Y .


> The median can be obtained recursively as follows. Pick the median of the sorted array A. This is just O(1) time as median is the n/2th element in the sorted array. Now compare the median of A, call is a∗ with median of B, b∗. We have two cases.

- a∗ < b∗ : In this case, the elements in B[n/2 ···n] are also greater than a . So the median cannot lie in either A[1 · · · n/2 ] or B[n/2 · · · n]. So we can just throw these away and recursively

- a∗ > b∗ : In this case, we can still throw away B[1··· n/2] and also A[ n/ · · · n] and solve a smaller subproblem recursively. 


In either case, our subproblem size reduces by a factor of half and we spend only constant time to compare the medians of A and B. So the recurrence relation would be T (n) = T (n/2) + O(1) which has a solution T (n) = O(log n).


divide and conquer

- 如果X[n/2] == Y[n/2]，则找到，return
- 如果X[n/2] < Y[n/2],找X[n/2+1….n]和Y[1,2…n/2]之间
- 否则找X[1..n/2]和Y[n/2…n]




但是实际上不同，这里需要考虑的问题更多：

- 两个数组长度不一样
- 并不是只找一个median，如果median有两个，需要算平均

思路

把它转化成经典的findKth问题

参考： <http://chaoren.is-programmer.com/posts/42890.html>


首先转成求A和B数组中第k小的数的问题, 然后用k/2在A和B中分别找。


比如k = 6, 分别看A和B中的第3个数, 已知 A1 < A2 < A3 < A4 < A5... 和 B1 < B2 < B3 < B4 < B5..., 如果A3 <＝ B3, 那么第6小的数肯定不会是A1, A2, A3, 因为最多有两个数小于A1, 三个数小于A2, 四个数小于A3。 关键点是从 k/2 开始来找。 



B3至少大于5个数, 所以第6小的数有可能是B1 (A1 < A2 < A3 < A4 < A5 < B1), 有可能是B2 (A1 < A2 < A3 < B1 < A4 < B2), 有可能是B3 (A1 < A2 < A3 < B1 < B2 < B3)。那就可以排除掉A1, A2, A3, 转成求A4, A5, ... B1, B2, B3, ...这些数中第3小的数的问题, k就被减半了。每次都假设A的元素个数少, pa = min(k/2, lenA)的结果可能导致k == 1或A空, 这两种情况都是终止条件。 


发问，为什么要从k/2开始寻找，依旧k = 6, 我可以比较A1 和 B5的关系么，可以这样做，但是明显的问题出现在如果A1 > B5，那么这个第6小的数应该存在于B6和A1中。

如果A1 < B5，这个时间可能性就很多了，比如A1 < A2 < A3 < A4 < B1 < B2，各种可能，无法排除元素，所以还是要从k/2开始寻找。

这个跟习题算法的区别是每次扔的东西明显少一些，但是k也在不断变小。下面的代码的时间复杂度是O(lg(m+n))


```python
class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        n = len(nums1) + len(nums2)
        if n % 2 == 1:
        	return self.findKth(nums1, nums2, n / 2 + 1)
        else:
        	smaller = self.findKth(nums1, nums2, n / 2)
        	bigger = self.findKth(nums1, nums2, n / 2 + 1)
        	return (smaller + bigger) / 2.0


    def findKth(self, A, B, k):
    	if len(A) == 0:
    		return B[k-1]
    	if len(B) == 0:
    		return A[k-1]
    	if k == 1 :
    		return min(A[0],B[0])


    	a = A[ k / 2 - 1 ] if len(A) >= k / 2 else None
    	b = B[ k / 2 - 1 ] if len(B) >= k / 2 else None

    	if b is None or (a is not None and a < b):
    		return self.findKth(A[k/2:], B, k - k/2)
    	return self.findKth(A, B[k/2:],k - k/2)  #这里要注意：因为 k/2 不一定 等于 (k - k/2), 


```
```python
#python3里面要用向下取整函数才可以AC，否则报错，TypeError: list indices must be integers or slices, not float

from math import floor
class Solution:
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        n = len(nums1) + len(nums2)
        if n % 2 == 1:
            return self.findKth(nums1, nums2, floor(n/2)+1)
        else:
            smaller = self.findKth(nums1, nums2, floor(n/2))
            bigger = self.findKth(nums1, nums2, floor(n/2)+1)
            return (smaller + bigger) / 2.0
    def findKth(self, A, B, k):

        if len(A) == 0:
            return B[k-1]
        if len(B) == 0:
            return A[k-1]
        if k == 1:
            return min(A[0], B[0])
        a = A[floor(k/2)-1] if len(A) >= k/2 else None
        b = B[floor(k/2)-1] if len(B) >= k/2 else None
        if b is None or (a is not None and a < b):
            return self.findKth(A[floor(k/2):], B, k - floor(k/2))
        else:
            return self.findKth(A, B[floor(k/2):], k - floor(k/2))
```

这个findKth的算法单独抽出来也是题目。
### 寻找最小的k个数

题目描述

输入n个整数，输出其中最小的k个。
分析与解法

## 解法一

要求一个序列中最小的k个数，按照惯有的思维方式，则是先对这个序列从小到大排序，然后输出前面的最小的k个数。
至于选取什么的排序方法，我想你可能会第一时间想到快速排序（我们知道，快速排序平均所费时间为n*logn），然后再遍历序列中前k个元素输出即可。因此，总的时间复杂度：```O（n * log n)+O(k)=O（n * log n）```。
## 解法二

咱们再进一步想想，题目没有要求最小的```k```个数有序，也没要求最后```n-k```个数有序。既然如此，就没有必要对所有元素进行排序。这时，咱们想到了用选择或交换排序，即：
1. 遍历```n```个数，把最先遍历到的k个数存入到大小为```k```的数组中，假设它们即是最小的```k```个数；
2. 对这```k```个数，利用选择或交换排序找到这k个元素中的最大值```kmax```（找最大值需要遍历这```k```个数，时间复杂度为```O（k））```；
3. 继续遍历剩余```n-k```个数。假设每一次遍历到的新的元素的值为```x```，把```x```与```kmax```比较：如果```x``` < ```kmax``` ，用```x```替换```kmax```，并回到第二步重新找出k个元素的数组中最大元素kmax‘；如果```x >= kmax```，则继续遍历不更新数组。
每次遍历，更新或不更新数组的所用的时间为```O（k）```或```O（0）```。故整趟下来，时间复杂度为```n*O（k）=O（n*k）```。
## 解法三

更好的办法是维护容量为k的最大堆，原理跟解法二的方法相似：
1. 用容量为```k```的最大堆存储最先遍历到的```k```个数，同样假设它们即是最小的```k```个数；
2. 堆中元素是有序的，令```k1<k2<...<kmax```（```kmax```设为最大堆中的最大元素）
3. 遍历剩余n-k个数。假设每一次遍历到的新的元素的值为```x```，把```x```与堆顶元素```kmax```比较：如果```x < kmax```，用```x```替换```kmax```，然后更新堆（用时```logk```）；否则不更新堆。
这样下来，总的时间复杂度:O（k+（n-k）*logk）=O（n*logk）。此方法得益于堆中进行查找和更新的时间复杂度均为：O(logk)（若使用解法二：在数组中找出最大元素，时间复杂度：O（k））。
解法四

在《数据结构与算法分析--c语言描述》一书，第7章第7.7.6节中，阐述了一种在平均情况下，时间复杂度为O（N）的快速选择算法。如下述文字：
- 选取S中一个元素作为枢纽元v，将集合S-{v}分割成S1和S2，就像快速排序那样
- 如果k <= |S1|，那么第k个最小元素必然在S1中。在这种情况下，返回QuickSelect(S1, k)。
- 如果k = 1 + |S1|，那么枢纽元素就是第k个最小元素，即找到，直接返回它。
- 否则，这第k个最小元素就在S2中，即S2中的第（k - |S1| - 1）个最小元素，我们递归调用并返回QuickSelect(S2, k - |S1| - 1)。此算法的平均运行时间为O(n)。

示例代码如下：
```C
//QuickSelect 将第k小的元素放在 a[k-1]  
void QuickSelect( int a[], int k, int left, int right )
{
    int i, j;
    int pivot;

    if( left + cutoff <= right )
    {
        pivot = median3( a, left, right );
        //取三数中值作为枢纽元，可以很大程度上避免最坏情况
        i = left; j = right - 1;
        for( ; ; )
        {
            while( a[ ++i ] < pivot ){ }
            while( a[ --j ] > pivot ){ }
            if( i < j )
                swap( &a[ i ], &a[ j ] );
            else
                break;
        }
        //重置枢纽元
        swap( &a[ i ], &a[ right - 1 ] );  

        if( k <= i )
            QuickSelect( a, k, left, i - 1 );
        else if( k > i + 1 )
            QuickSelect( a, k, i + 1, right );
    }
    else  
        InsertSort( a + left, right - left + 1 );
}
```
这个快速选择SELECT算法，类似快速排序的划分方法。N个数存储在数组S中，再从数组中选取“中位数的中位数”作为枢纽元X，把数组划分为Sa和Sb俩部分，Sa<=X<=Sb，如果要查找的k个元素小于Sa的元素个数，则返回Sa中较小的k个元素，否则返回Sa中所有元素+Sb中小的k-|Sa|个元素，这种解法在平均情况下能做到O(n)的复杂度。
更进一步，《算法导论》第9章第9.3节介绍了一个最坏情况下亦为O(n)时间的SELECT算法，有兴趣的读者可以参看。

给定两个已经排序好的数组，求第k大的，算法有O(m+n).类似merge sort的原理。否则利用的就是之上提到的，利用已经有序的原理，然后每次丢。

之所以这里还有一个丢弃条件是b is None 丢A的一部分，是因为B的数组长度是有限的，这个时候很明显丢A的k/2是不影响的，因为无论B[-1]是如何大或者小，因为整个B的长度没有达到k/2小，所以丢掉的这部分最大的A[k/2-1]也不可能是第k个，因为即使整个B都比A[k/2-1]小，拼起来也不能使A[k/2-1]第k大，所以可以放心丢弃。


这里是两个sorted list/array findKth，想到了类似的题目，如果给一个n个linked list，findKth，能想到的办法也只能是用heap吧，类似merge k sorted lists.


再写一个O(m+n)类似merge sort的也可以AC的代码

```python
class Solution(object):
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """
        def findKth(A, pa, B, pb, k):
            res = 0
            m = 0
            while pa < len(A) and pb < len(B) and m < k:
                if A[pa] < B[pb]:
                    res = A[pa]
                    m += 1
                    pa += 1
                else:
                    res = B[pb]
                    m += 1
                    pb += 1
                    
            while pa < len(A) and m < k:
                res = A[pa]
                pa += 1
                m += 1


            while pb < len(B) and m < k:
                res = B[pb]
                pb += 1
                m += 1
            return res

        n = len(nums1) + len(nums2)
        if n % 2 == 1:
        	return findKth(nums1,0, nums2,0, n / 2 + 1)
        else:
        	smaller = findKth(nums1,0, nums2,0, n / 2)
        	bigger = findKth(nums1,0, nums2,0, n / 2 + 1)
        	return (smaller + bigger) / 2.0

```
