题目
统计一个数字在排序数组中出现的次数。
解析
预备知识
在排序数组中,高效的查找指定数字可以采用二分查找,该方法的复杂度为O(logn),它可以看做有序数组中查找的标配!思路如下:
1. 首先有个指针start,end分别指向数组的开头和结尾,k为待查数字。
2. 判断start是否大于end,若是则结束,否则进行第三步
3. 求出中间索引mid = start + ((end - start) >> 1)
4. 判断mid所指向的值与k比较,若大于,则说明k位于mid前面,所以更新end = mid - 1
,跳到第2步;若小于则说明,k位于mid后面,所以更新start = mid + 1
,跳到第2步;若相等,则说明找到,直接返回当前mid索引即可
private static int binarySearch(int[] array, int k) {
int start = 0, end = array.length - 1;
while(start <= end) {
int mid = start + ((end - start) >> 1);
if(array[mid] < k) {
start = mid + 1;
} else if(array[mid] > k) {
end = mid + 1;
} else {
return mid;
}
}
return -1;
}
思路一
既然我们要统计指定数字在数组出现的次数,我们先根据二分查找找到该数字的位置。因为在排序数组中,相同的数字是连续在一起的,所以对得到的位置向前和向后搜查并统计即可。
public static int GetNumberOfK3(int [] array , int k) {
if(array == null || array.length == 0) {
return 0;
}
int index = binarySearch(array, k);
int count = 0;
if(index != - 1) {
count++;
//向前搜查
for(int i = index - 1; i >= 0; i--) {
if(array[i] == k) {
count++;
}
}
//向后搜查
for(int i = index + 1; i < array.length; i++) {
if(array[i] == k) {
count++;
}
}
}
return count;
}
思路二
但是思路一有个弊端,我们虽然利用高效的二分找到该数字的任意的一个位置,但是如果连续的数字有很多,算法复杂度退化为O(n),显然是不能接受。我们发现只要确定了数字出现的第一次的位置和最后一次的位置,做差即可确定其出现的总次数了。所以问题转化为如何求数字第一次出现的位置和最后一次的位置。
求数字第一次出现的位置:改变二分查找
对于经典二分查找中,当mid所指的内容等于给定的值的时候,我们令end = mid - 1
;这样可以进一步向前缩小搜查的范围,因为当前的搜查到的位置可能不是第一次出现,所以要继续向该位置之前查找。而最后结束循环的时候,start指向的位置就是数字第一次出现的位置或者为刚刚大于该值的位置(这种情况是数组中不存在该值)。
/**
* 变形的二分查找
* @param array
* @param k
* @return start指向k第一次出现的位置,或者指向刚刚大于k的位置(不存在k)
*/
public static int findFirst(int[] array, int k) {
int start = 0, end = array.length - 1;
while(start <= end) {
int mid = start + ((end - start) >> 1);
if(array[mid] < k) {
start = mid + 1;
} else {
end = mid - 1;
}
}
return start;
}
求数字最后一次出现的位置:改变二分查找
对于经典二分查找中,当mid所指的内容等于给定的值的时候,我们令start = mid + 1
;这样可以进一步向前缩小搜查的范围,因为当前的搜查到的位置可能不是最后一次出现,所以要继续向该位置之后查找。而最后结束循环的时候,end指向的位置就是数字最后一次出现的位置或者为刚刚小于该值的位置(这种情况是数组中不存在该值)。
/**
* 变形的二分查找
* @param array
* @param k
* @return end指向k最后一次出现的位置,或者指向刚刚小于k的位置(不存在k)
*/
public static int findLast(int[] array, int k) {
int start = 0, end = array.length - 1;
while(start <= end) {
int mid = start + ((end - start) >> 1);
if(array[mid] > k) {
end = mid - 1;
} else {
start = mid + 1;
}
}
return end;
}
等于得到的first和last索引后,我们取差值 + 1即可得到出现的次数。
1. 比如若存在2个k,first位置为2,last位置为3,那么次数为:3 - 2 + 1 = 2
2. 比如不存在k, first位置为3, last位置为2, 那么次数为:2 - 3 + 1 = 0
显然,k是否存在都符合差值 + 1来求长度。
public static int GetNumberOfK2(int [] array , int k) {
if(array == null || array.length == 0) {
return 0;
}
int firstOcur = findFirst(array, k);
int lastOcur = findLast(array, k);
return lastOcur - firstOcur + 1;
}
总结
有序数组的查找首选二分查找。