描述
给定一个长度为 n 的非降序数组和一个非负数整数 k ,要求统计 k 在数组中出现的次数
数据范围:0 < n < 1000 , 0 < k < 100,数组中每个元素的值满足 0 < val < 100
要求:空间复杂度 O(1),时间复杂度 O(logn)
很明显,给定的数组是非降序的,即数组是升序的,只不过存在相同大小的元素。这道题就是要先使用二分查找先定位到元素k在数组中的位置,然后从该位置处分别向左和向右两个方向分别寻找和自己相同大小的元素并统计出个数。因为数组是有序的,所以每个方向上只要找到和自己不一样大的元素就停止这个方向的查找。
主要考察的是会不会写二分查找,和写二分查找时的一些细节点,比如递归调用,子数组首尾临界值处理,和递归调用如何退出。下面直接贴代码,细节点单独写文章来梳理说明。
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if (array == null || array.length == 0) {
return 0;
}
//先二分查找k在数组中出现的位置,再从该位置左右遍历出k出现的数量
int length = array.length;
int index = binarySearch(array, k, 0, length);
if (-1 == index) {
return 0;
}
int i = index - 1;
int j = index + 1;
int count = 1;
boolean findLeft = true;
boolean findRight= true;
while(findLeft || findRight) {
if (findLeft) {
if (i >= 0 && k == array[i]) {
count++;
i--;
} else {
findLeft = false;
}
}
if (findRight) {
if (j < length && k == array[j]) {
count++;
j++;
} else {
findRight = false;
}
}
}
return count;
}
//二分查找算法
public int binarySearch(int [] array, int k, int start, int end) {
if (start == end) {
return -1;
}
int mid = (start + end) / 2;
if (k == array[mid]) {
return mid;
} else if (k < array[mid]) {
end = mid;
} else {
start = mid + 1;
}
int index = binarySearch(array, k, start, end);
return index;
}
}
网上看到一种更好的方法
public class Solution {
//二分查找
private int bisearch(int[] data, double k){
int left = 0;
int right = data.length - 1;
//二分左右界
while(left <= right){
int mid = (left + right) / 2;
if(data[mid] < k)
left = mid + 1;
else if(data[mid] > k)
right = mid - 1;
}
return left;
}
public int GetNumberOfK(int [] array , int k) {
//分别查找k+0.5和k-0.5应该出现的位置,中间的部分就全是k
return bisearch(array, k + 0.5) - bisearch(array, k - 0.5);
}
}
可以看到,这是一个二分查找算法的变形。
思路:
因为data是一个非降序数组,它是有序的,这种时候我们可能会想到用二分查找。但是一个数组可能有多个k,而且我们要查找的并非常规二分法中k出现的位置,而是k出现的左界和k出现的右界。要是能刚好找到恰好小于k的数字位置和恰好大于k的数字的位置就好了。
再有因为数组中全是整数,因此我们可以考虑,用二分查找找到k+0.5应该出现的位置和k−0.5应该出现的位置,二者相减就是k出现的次数。
1 |
|
while(left <= right){
int mid = (left + right) / 2;
if(data[mid] < k)
left = mid + 1;
else if(data[mid] > k)
right = mid - 1;
}
我们传入的是double型数字,而数组是整形的,所以数组中肯定找不到该元素。上面while循环结束条件是left>right。
仔细思考了一下while循环的演进过程,发现它其实开始会一直只朝一个方向查找,比如left不断增大;等该方向不满足时,就会一直往另外一个方向查找,比如right不断减小。也就意味着left先增大,后面right减小,直到left>right,这时的left就是K最应该出现的位置。即数组中两个方向先后朝着这个数应该出现的位置上逼近。