正在找工作的猿六凯在投出无数份简历后,准备打开电脑看会电影放松下。突然接到面试官的电话,急忙跑到厕所接听。
黑脸面试官
小猿同学,看你的简历说算法掌握的比较扎实,我来给你出道算法题。
黑脸面试官
我们有一个无序的,没有重复元素的数组,现在我们需要找出第k小的数字。
黑脸面试官
比如我们的无序数组 7 3 5 1 6。需要找出第3小数。我们可以看出来,1是第一小的数,3是第二小的数,5是第三小的数。5就是我们要找的。
猿六凯
这个很简单呀,假设我们的数组是a,把数组排下序,然后a[k-1]就是我们要找的元素。
猿六凯
排序的时间复杂度是O(nlogn),取元素的时间复杂度是O(1),所以我们的总时间复杂度是O(nlogn)。
黑脸面试官
有没有其他的方法,比如用用快排的思想。
猿六凯
快排的思想??恩,我想想。
猿六凯
快排的思想是分治,每次把数组分成两部分,左边部元素全都小于等于右边元素。
猿六凯
我们要找第k小的数,这个数字要么在分开的数组左边,要么在右边。
猿六凯
如果左边数组元素个数大于等于k,那么第k小的元素就在左边,我们在左边数组中找第k小的元素就可以。
猿六凯
如果左边数组元素个数小于k,那么第k小的元素就在右边。假设我们左边数组右x个元素,我们在右边数组中找第k-x小的元素就可以。
猿六凯
这样就能把寻找的范围一直缩小。直到最后,我们就在某个为1的范围内找第一小的数,这个时候,这个元素就是我们要找的元素了。
黑脸面试官
恩,你手动模拟下7 3 5 1 6 找出第3小数字的过程。
猿六凯
我以30年单手手速化了下面动图。
黑脸面试官
恩,写下代码吧。
#include using namespace std;const int N = 100010;int a[N];int find(int a[], int l, int r, int k){ if(l == r && k == 1) return a[l]; int x = a[(l + r) >> 1]; int i = l - 1, j = r + 1; while(i < j){ while(a[++i] < x); while(a[--j] > x); if(i < j ) swap(a[i], a[j]); } if(j- l + 1 >= k ) return find(a, l, j, k); return find(a, j + 1, r, k - j + l - 1); }int main(){ int n, k; cin >> n >> k; if(k > n) return -1; for(int i = 0; i < n; i++){ cin>>a[i]; } cout << find(a, 0, n-1, k); return 0;}
黑脸面试官
分析下时间复杂度和空间复杂度。
猿六凯
时间上,我们花费的是不断将搜寻范围缩小。
猿六凯
在最好情况下,每次能将搜寻范围减少到原来的一般。所以第一次缩小范围花费的是O(n),第二次缩小范围花费的是O(n/2),第三次缩小范围花费的是O(n/4)。
猿六凯
直到最后搜寻范围为1,我们就找到了答案。
猿六凯
所以时间复杂度是:O(n)+O(n/2^1)+O(n/2^2)+...O(n/2^logn)
猿六凯
n+n/2+n/4+...+n*2^logn = n*(1+1/2+1/4+..2^logn) = n*(1-1/2+1/2-1/4 +....+ n/2^logn-1 - 1/2^logn)=n*(1 - 1/2^logn)= O(n)。所以最好情况下,时间复杂度是O(n)。
猿六凯
最坏情况下,每次只能将范围缩小1,所以时间复杂度是:O(n + n-1 + n-2 + ... + 1) =O( (n+1)/2*n) = O(n^2)。
黑脸面试官
恩,还不错,那还有没有其他方法。
猿六凯
暂时想不到了
黑脸面试官
恩,今天先到这,等下一轮面试吧。
猿六凯
恩恩,谢谢黑脸面试官。
黑脸面试官
你说谁脸黑,你没下一次面试了。
欢迎订阅,每周更新面试高频算法题。从思路,代码,时间复杂度等多方面进行分析。