数字在排序数组中出现次数
- 题目:统计一个数字在一个排序数组中出现的次数。例如,输入数组{1,2,3,3,3,3,3,4,5} 和数字3,由于3 在数组中出现的次数是5,因此返回5
简单方案一
- 既然输入的数组是有序的,那么最简单的方式是一次遍历,统计出需要的数字出现的个数,时间复杂度是O(n),实现方法如下:
**
* 查询有序数组中数字k 出现的次数
*
* @author liaojiamin
* @Date:Created in 16:08 2021/6/24
*/
public class GetNumberOfK {
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 9, 33, 44, 55, 56, 56, 56, 56, 56, 57};
System.out.println(countK(array, 0));
}
/**
* 方法一,遍历统计 O(n)
*/
public static Integer countK(int[] array, int k) {
if (array == null || array.length <= 0) {
return -1;
}
int count = 0;
for (int i = 0; i < array.length; i++) {
if (array[i] == k) {
count++;
}
}
return count;
}
}
方法二二分查找
- 有序数组,而且是查询,那么时间效率最高的就是二分查找了,分析流程如下:
- 如上案例中二分查找找出其中一个3 的位置,标记为position
- 此时可能前后都还存在3,那么从position向前遍历,查找之前的3,得到firstK
- 从position向后遍历,查找之后的3,得到lastK
- 那么k出现的次数n = lastK - firstK + 1
- 如上分析有如下代码:
/**
* 查询有序数组中数字k 出现的次数
*
* @author liaojiamin
* @Date:Created in 16:08 2021/6/24
*/
public class GetNumberOfK {
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 9, 33, 44, 55, 56, 56, 56, 56, 56, 57};
System.out.println(binarySearchK(array, 0));
}
/**
* 方法二:二分查找找出k,得到k后,左右遍历k,直到找到,firstK和lastK
* O(n)
*/
public static Integer binarySearchK(int[] array, int k) {
Integer positionK = binarySearch(array, 0, array.length - 1, k);
if (positionK < 0) {
return -1;
}
Integer firstK = positionK;
Integer lastK = positionK;
for (int i = positionK; i < array.length; i++) {
if (array[i] == k) {
lastK = i;
}
}
for (int i = positionK; i >= 0; i--) {
if (array[i] == k) {
firstK = i;
}
}
return lastK - firstK + 1;
}
public static Integer binarySearch(int[] array, int left, int right, int k) {
if (array == null || left < 0 || right > array.length - 1 || left > right) {
return -1;
}
int middle = (left + right) / 2;
if (array[middle] == k) {
return middle;
}
if (array[middle] > k) {
return binarySearch(array, left, middle - 1, k);
}
if (array[middle] < k) {
return binarySearch(array, middle + 1, right, k);
}
return -1;
}
}
- 如上算法因为k出现的次数可能是n次,那么我们向前向后遍历的次数可能就是n, 那么时间复杂度还是O(n),并没有达到优化的目的。此算法中实际主要消耗在如何确定重复数字的第一个k与最后一个k的位置上。
方案三纯二分查找
-
方案二中既然可以通过二分查找找到其中一个3 ,那么我们是否能通过二分查找找到firstK和lastK
-
分析如下:
- 先查找第一个k, 标记位置为position
- 当地position-1 个位置的值也是k的时候,我们认为k之前还有重复的数字k存在,依然用二分查找继续找 left ~ k-1 位置中的k
- 持续以上步骤,直到position-1 位置不是k为止,我们得到firstK = position
- 同理,如果position+1位置也是k,那么我们递归position+1 ~ right的数组中二分查找k,得到lastK
- 如上我们用纯二分查找的方式找出了lastK,firstK
-
经如上分析有如下代码:
/**
* 查询有序数组中数字k 出现的次数
*
* @author liaojiamin
* @Date:Created in 16:08 2021/6/24
*/
public class GetNumberOfK {
public static void main(String[] args) {
int[] array = new int[]{1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 9, 33, 44, 55, 56, 56, 56, 56, 56, 57};
System.out.println(binarySearchAllK(array, 0));
}
/**
* 方法三,还是二分查找,分别找出firstK, lastK
*/
public static Integer binarySearchAllK(int[] array, int k) {
if (array == null || array.length <= 0) {
return -1;
}
int firstK = binarySearchFirstK(array, 0, array.length - 1, k);
int lastK = binarySearchLastK(array, 0, array.length - 1, k);
if (firstK < 0 && lastK < 0) {
return -1;
}
return lastK - firstK + 1;
}
/**
* 二分查找第一个k
*/
public static Integer binarySearchFirstK(int[] array, int left, int right, int k) {
if (array == null || left < 0 || right > array.length - 1 || left > right) {
return -1;
}
int middle = (left + right) / 2;
if (array[middle] == k) {
if (middle - 1 >= left && array[middle - 1] == k) {
return binarySearchFirstK(array, left, middle - 1, k);
} else {
return middle;
}
}
if (array[middle] < k) {
return binarySearchFirstK(array, middle + 1, right, k);
}
if (array[middle] > k) {
return binarySearchFirstK(array, left, middle - 1, k);
}
return -1;
}
/**
* 二分查找最后一个k
*/
public static Integer binarySearchLastK(int[] array, int left, int right, int k) {
if (array == null || left < 0 || right > array.length - 1 || left > right) {
return -1;
}
int middle = (left + right) / 2;
if (array[middle] == k) {
if (middle + 1 <= right && array[middle + 1] == k) {
return binarySearchLastK(array, middle + 1, right, k);
} else {
return middle;
}
}
if (array[middle] > k) {
return binarySearchLastK(array, left, middle - 1, k);
}
if (array[middle] < k) {
return binarySearchLastK(array, middle + 1, right, k);
}
return -1;
}
}
- 如上实现中firstK,lastK都是使用二分查找在数组中查找,并且二分查找的总量不会大于整个数组的量n,他们两次查询的时间复杂度都是O(logn),因此总的时间复杂度就是O(logn),达到了我们优化的目的。