三、查找和排序
查找不外乎顺序查找、二分查找、哈希表查找和二叉排序树。排序比哈希稍微复杂,插入排序、冒泡、归并排序、快速排序等不同算法的优劣与代码编写应该要非常熟悉。这些基于比较的算法,平均时间复杂度最好为O(nlogn)。如果需要线性时间内排序,那么需要考虑桶排之类的算法了。
面试题8:旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组最小为1。
解析:这道题和之前遍历二维数组一样,直观的解法很容易,遍历一遍数组找到最小的值。但是如果想要更高效的算法必然要观察数据的规律。由于这是查找,而且数组基本算是有序,二分查找算是不错的选择!
int Min(int* a, int len){
if(a==NULL || len<=0)
throw new std::exception("Invaild input");
int low=0;
int high=len-1;
while(a[low]>=a[high]){ //(1)循环条件
if(high-low==1) return a[high]; //(2)结束条件
int mid=(low+high)/2;
if(a[mid]==a[low]&&a[high]==a[mid]){
cout<<"只能顺序找"<<endl;
}
if(a[mid]>a[low]){
low=mid; //(3)
}
else if(a[mid]<a[high]){
high=mid; //(4)
}
}
return a[low];
}
这道题算是一种改进版的二分查找吧,原始的二分查找是要找到某个值,通过将中间值和目标值val比较,缩小搜索范围。而这里实质是要找到一个范围,通过将中间的值和两边值比较,缩小范围。原始二分查找的循环条件是low<=high;而改进版的是a[low]>=a[high],否则没有搜索意义了。
int bisearch(int a[],int low, int high, int val){
while(low<=high){
int mid=(low+high)/2;
if(a[mid]<val) high=mid-1;
else if(a[mid]>val) low=mid+1;
else return mid;
}
return -1;
}
面试题38:数字在排序数组中出现的次数
题目:统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4.
解析:这里是在有序的数组里统计某个数出现的个数。虽然顺序查询也可以求得所需的结果,但是更高效的算法可以借助二分查找。基本思路是找到第一个k和最后一个k出现的位置,然后计算k出现的个数。
int getFirstK(int *a, int len, int k){
int low=0;
int high=len-1;
while(low<=high){
int mid=(low+high)/2;
if(a[mid]==k){
if(mid==0|| mid>0&&a[mid-1]!=k)
return mid;
else
high=mid-1;
}
else if(a[mid]<k) low=mid+1;
else high=mid-1;
}
return -1;
}
int getLastK(int *a, int len, int k){
int low=0;
int high=len-1;
while(low<=high){
int mid=(low+high)/2;
if(a[mid]==k){
if(mid==len-1|| mid<len-1&&a[mid+1]!=k)
return mid;
else
low=mid+1;
}
else if(a[mid]<k) low=mid+1;
else high=mid-1;
}
return -1;
}
int getNumberOfK(int *a, int len, int k){
int number =0;
if(a!=NULL&&len>0){
int first=getFirstK(a,len,k);
int last=getLastK(a,len,k);
if(first>-1&&last!=-1)
number=last-first+1;
}
return number;
}
虽然这道题并不是很明显的二分查找题目,但是如果对二分查找理解深刻的话,应该会想到。再说了,能提高搜索效率的,除了借助额外的hash表,只能想到二分查找了~