高频题目
给定两个已经排序的数组(假设按照升序排列),然后找出第K小的数。比如数组A = {1, 8, 10, 20}, B = {5, 9, 22, 110}, 第 3 小的数是 8.。要求时间复杂度O(logN),空间复杂度O(1)。
O(K)的时间复杂度
- 用两个指针
pa
、pb
分别指向arr1
、arr2
。- 在初始状态,两个指针分别指向第一个起始值,即pa = 0, pb = 0。
- 然后,我们开始进行比较,如果A数组的值比B数组的值小, 把A数组的指针pa向前移动,然后再进行比较。
- 每次移动,我们都能够得到当前第 i 小的值(随着pa,pb 的移动,i 会逐渐变大)。
- 比如对于数组A = {1, 8, 10, 20}, B = {5, 9, 22, 110}
- 初始时,pa = 0; pb = 0,因为(A[pa] = 1) < (B[pb] = 5), 所以,第 1 小的值为 1
- 这个时候,我们会把 pa 向右移动,即 pa = 1, 并且 pb 保持不变: pb = 0.
- 然后再次比较A[pa] 和 B[pb] 的值,因为 (A[pa] = 8) > (B[pb] = 5), 所以,第 2 小的值是 5, 然后,我们增加 pb 的值。
- 请注意,如果我们用一个变量来保存当前第 i 小的值 (随着pa, pb的移动,i 的值也会增加,那个变量保存的值也会改变)。那么,当pa + pb = k 的时候,那么那个变量保存的一定是第 k 小的值。
- 这里要注意的是,当一个数组的指针已经“到头”了,这个时候怎么进行越界处理呢?我们这里可以用一个简单的判断语句就可以处理了。
int a = (pa == arr1.size()) ? INT32_MAX : arr1[pa];
int b = (pb == arr2.size()) ? INT32_MAX : arr2[pb];
实现如下:
class Solution {
public:
int kthTwoSortedArray(std::vector<int> A, std::vector<int>B, int k){
assert(A.size() != B.size() && !B.empty() && k >= 0);
int pa = 0;
int pb = 0;
int kthValue = 0; //store the kth value
while (pa + pb != k){
int a = (pa == A.size()) ? INT32_MAX : A[pa];
int b = (pb == B.size()) ? INT32_MAX : B[pb];
if(a < b){
kthValue = a;
pa++;
}else{
kthValue = b;
pb++;
}
}
return kthValue;
}
};
O(logN)的时间复杂度
在上面的程序里,我们是逐一比较数组A 和数组B的值,但是,这样做还是太慢了,因为两个数组是已经排过序的,我们完全可以利用二分查找的方法来解决这样的问题。
二分算法的思路是:通过不断的缩小问题的规模,也就是元素的查找范围,来提升算法的执行效率。
在具体介绍怎么做之前,先讲一些预备知识。
-
对于数组 A , B A,B A,B:
- 如果 A [ p a − 1 ] < B [ p b ] A[pa - 1] < B[pb] A[pa−1]<B[pb] && B [ p b ] < A [ p a ] B[pb] < A[pa] B[pb]<A[pa]
- 那么 B [ p b ] B[pb] B[pb] 一定是第 p a + p b + 1 pa + pb + 1 pa+pb+1 小的数
-
比如数组A = {1, 8, 10, 20}, B = {5, 9, 22, 110},pa = 2, pb = 1,
- 这时, ( A [ p a − 1 ] = 8 ) < ( B [ p b ] = 9 ) (A[pa - 1] = 8) < (B[pb] = 9) (A[pa−1]=8)<(B[pb]=9) && ( B [ p b ] = 9 ) < ( A [ p a ] = 10 ) (B[pb] = 9) < (A[pa] =10) (B[pb]=9)<(A[pa]=10)
- 那么, B [ p b ] = 9 B[pb] = 9 B[pb]=9 一定是第 p a + p b + 1 = 4 pa+pb+1 = 4 pa+pb+1=4 小的数。
-
换一句话说,如果我们要找第 k 小的数,那么,
- 下面两个条件必须成立
- p a + p b = k − 1 pa + pb = k - 1 pa+pb=k−1。
- A [ p a − 1 ] < B [ p b ] A[pa - 1] < B[pb] A[pa−1]<B[pb] && B [ p b ] < A [ p a ] B[pb] < A[pa] B[pb]<A[pa]或者 B [ p b − 1 ] < A [ p a ] B[pb - 1] < A[pa] B[pb−1]<A[pa] && A [ p a ] < B [ p b ] A[pa] < B[pb] A[pa]<B[pb]中的其中一个必须成立。
- 如果不成立, 我们需要移动pa 和 pb 的位置来使得其中的一个式子成立 (参看代码)。这是本题算法的本质。
- 下面两个条件必须成立
-
但是,这里也会出现”边界“问题
- 比如,k = 1, 那么 pa + pb = 1 - 1, 即 pa + pb = 0
- 因为 pa >= 0, pb >=0, 所以,我们得到 pa = 0, pb = 0.
- 那么这样的话, 求A [pa-1]值的时候 就会有exception.
- 同理,对于数组A = {1, 8, 10, 20}, B = {5, 9, 22, 110}
- 当k = 8 的时候, pa + pb = 8 - 1, 即 pa + pb = 7
- 但是,pa <= 3, pb <= 3。
- 不存在A[4]、B[4]之类的情况
- 比如,k = 1, 那么 pa + pb = 1 - 1, 即 pa + pb = 0
-
边界的处理是这道题最最麻烦的地方。对于这个问题,我们用下面这段代码来解决。
int Ai_1 = (pa == 0) ? Integer.MIN_VALUE : A[pa-1];
int Bj_1 = (pb == 0) ? Integer.MIN_VALUE : B[pb-1];
int Ai = (pa == A.length) ? Integer.MAX_VALUE : A[pa];
int Bj = (pb == B.length) ? Integer.MAX_VALUE : B[pb];
-
通过上面这段代码,我们实际上是把数组延长了,每个数组多了两个值Integer.MIN_VALUE,Integer.MAX_VALUE
- 这样,当pa = 0 是,我们也可以得到A[pa - 1] 的值:Ai_1 = ((pa == 0) ? Integer.MIN_VALUE : A[pa-1]);
- 当 pa =A.length 时,我们可以得到 A[pa]的值:Ai =(pa == A.length) ? Integer.MAX_VALUE : A[pa]。
-
这样,我们就不用担心越界的问题了。
有了上面的分析,代码就容易写出来了。
- 在程序里,我们设置 pa 的初始值为Math.min(A.length, k - 1)
- 然后,通过增加或者减少pa 的值,来使得 B[pb] < A[pa] && B[pb] > A[pa - 1] 或者A[pa] < B[pb] && A[pa] > B[pb - 1] 成立
- 代码中, delta 指的是 pa 的变化量,每次递归以后,delta的值变成一半。
class Solution {
int process(std::vector<int> &A, std::vector<int> &B, int pa, int delta, int k){
int pb = (k - 1) - pa;
int Ai_1 = ((pa == 0) ? INT32_MIN : A[pa-1]);
int Bj_1 = ((pb == 0) ? INT32_MIN : B[pb-1]);
int Ai = ((pa == A.size()) ? INT32_MAX : A[pa]);
int Bj = ((pb == B.size()) ? INT32_MAX : B[pb]);
//满足其中之一条件,就返回
if (Bj_1 <= Ai && Ai <= Bj) return Ai;
if (Ai_1 <= Bj && Bj <= Ai) return Bj;
//delta表示pa的变化量(增加或者减少)
//如果 Ai > Bj, 我们要缩小pa的值,即 pa = pa - delta
//因为 pb = (k - 1) - pa, 所以,如果delta的值太大,
//pa会变得很小,因而 可能会导致 pb > B.length. 所以需要处理一下。
// 对于pa = pa + delta 的处理也是一样
if (Ai > Bj) {
pa = ((k - 1) - (pa - delta) > B.size()) ? k - 1 - B.size() : pa - delta;
return process(A, B, pa, (delta + 1) / 2, k);
} else {
pa = (pa + delta > A.size()) ? A.size() : pa + delta;
return process(A, B, pa, (delta + 1) / 2, k);
}
}
public:
int findKthSmallest(std::vector<int> A, std::vector<int>B, int k){
int N = A.size();
int pa = std::min(N, k - 1);
int delta = (pa + 1) / 2;
return process(A, B, pa, delta, k);
}
};