1. 题目:给定一个无序整数数组,返回这个数组中第k小的数。
2. 分析:最平常的思路是将数组排序,最快的排序是快排,然后返回已排序数组的第k个数,算法时间复杂度为O(nlogn),空间复杂度为O(1)。使用快排的思想,但是每次只对patition之后的数组的一半递归,这样可以将时间复杂度将为O(n)。
具体的思路:将数组按照第一个数字first进行划分,将比first小的放在左边,比first大的放在右边,first放中间。返回patition之后first的下标j。如果此时j+1==k(+1是因为数组下标从0开始)那么说明a[j]就是要找的第k个数。如果j+1<k,递归查找左半部分;如果j+1>k,递归查找右半部分。代码如下:
1 #include <stdio.h> 2 #include <iostream> 3 #include <string> 4 #include <cassert> 5 #include <fstream> 6 #include <cmath> 7 using namespace std; 8 9 int patition(int* num, int start,int end) 10 { 11 int p=num[start]; 12 int i=start; 13 int j=end+1; 14 while (true) 15 { 16 while (num[++i]<=p && i<=end) 17 { 18 } 19 while (num[--j]>p && j>=start) 20 { 21 } 22 if (i>=j) 23 { 24 break; 25 } 26 swap(num[i],num[j]); 27 } 28 num[start]=num[j]; 29 num[j]=p; 30 31 for (int i=start;i<=end;i++) 32 { 33 cout<<num[i]<<" "; 34 } 35 cout<<endl; 36 return j; 37 } 38 39 //求一个无序数组的第k小的数字 40 int findKthSmall(int* num, int start,int end,int k) 41 { 42 int length=end-start+1; 43 assert(num && length>0 && k>0 && k<=end+1); 44 int j=patition(num,start,end); 45 if (k==(j+1)) 46 { 47 return num[j]; 48 } 49 else if (k<(j+1)) 50 { 51 return findKthSmall(num,start,j-1,k); 52 } 53 else 54 { 55 return findKthSmall(num,j+1,end,k); 56 } 57 } 58 59 int main() 60 { 61 enum{aLength=8}; 62 int a[aLength]={12,4,8,8,8,1,15,9}; 63 for (int i=0;i<aLength;i++) 64 { 65 cout<<"The "<<i+1<<"th small number is :"<<findKthSmall(a,0,aLength-1,i+1)<<endl; 66 } 67 return 0; 68 }
复杂度分析:平均时间复杂度O(n),这个见文章分析《递归式复杂度的计算》。
上面两种算法都是要改变原数组结构的(或者将原数组拷贝一份),下面的算法不需要改变原数组的结构。
思路:维持一个大小为k的大顶堆,遍历一次数组,如果数组中的元素比堆顶的元素小,那么就更新堆。最后堆中存放的是数组中的前k小元素。堆顶元素即为要求的第k小个数。
分析:这种算法不需要改变原数组结构,但是需要额外维持一个大小为O(k)的堆,时间复杂度为(nlogk)。当k比n小的多的时候,这个算法也是一个很好的选择。