004._median_of_two_sorted_arrays.md 11.7 KB
Newer Older
1
### 4. Median of Two Sorted Arrays
K
KEQI HUANG 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

题目:
<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)
100
    	return self.findKth(A, B[k/2:],k - k/2)  #这里要注意:因为 k/2 不一定 等于 (k - k/2), 
101 102 103


```
104
```python
105
#python3里面要用向下取整函数才可以AC,否则报错,TypeError: list indices must be integers or slices, not float
K
KEQI HUANG 已提交
106

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
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))
K
KEQI HUANG 已提交
136 137 138
```

这个findKth的算法单独抽出来也是题目。
139
### 寻找最小的k个数
140 141 142 143 144 145

题目描述

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

146
## 解法一
147 148 149

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

咱们再进一步想想,题目没有要求最小的```k```个数有序,也没要求最后```n-k```个数有序。既然如此,就没有必要对所有元素进行排序。这时,咱们想到了用选择或交换排序,即:
153 154 155
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```,则继续遍历不更新数组。
156
每次遍历,更新或不更新数组的所用的时间为```O(k)```或```O(0)```。故整趟下来,时间复杂度为```n*O(k)=O(n*k)```。
157
## 解法三
158 159

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

在《数据结构与算法分析--c语言描述》一书,第7章第7.7.6节中,阐述了一种在平均情况下,时间复杂度为O(N)的快速选择算法。如下述文字:
167 168 169 170 171
- 选取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)。

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
示例代码如下:
```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
KEQI HUANG 已提交
208 209 210

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

211
之所以这里还有一个丢弃条件是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大,所以可以放心丢弃。
K
KEQI HUANG 已提交
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260


这里是两个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

```