统计一个数字在排序数组中出现的次数。例如,输入排序数组{1,2,3,3,3,3,4,5}
和数字3,由于3在这个数组中出现了4次,因此输出4.
由于数组是单调递增的,所以可以采用二分查找
思路:
利用二分查找法的变形分别找到第一个要找的值的下标和最后一个要找的值的下标,时间复杂度为 O(logn)。
1.如何利用二分查找找到第一个k?
二分查找算法总是先拿数组中间的数组和k作比较.
如果中间数字比k大,那么k只可能出现数组的前半段,下一轮只在数组的前半段查找就可以了.
如果中间数字比k小,那么k只可能出现数组的后半段,下一轮只在数组的后半段查找就可以了.
如果中间数组和k相等,先判断这个数字是不是第一个k.
如果位于中间数字前面的一个数字不是k,那么此时中间的数字刚好就是第一个k.
如果中间数字的前面一个数字也是k,也就是说第一个k肯定在数组的前半段,下一轮仍然需要在数组的前半段查找.
2.如何利用二分法找到最后一个k?
如果中间数字比k大,那么k只能出现在数组的前半段.
如果中间数字比k小,那么k只能出现在数组的后半段.
如果中间数子和k相等,需要判断这个数字是不是最后一个k.
如果位于中间数字后面一个数字不是k,那么此时中间的数字刚好就是最后一个k.
如果中间数字的后面一个数字也是k,也就是说第一个k肯定在数组的后半段,下一轮仍然需要在数组的后半段查找.
package jianZhiOffer;
/*
* 面试题53:在排序数组中查找数字
* 题目一:数字在排序数组中出现的次数
* 统计一个数字在排序数组中出现的次数。例如,输入排序数组{1,2,3,3,3,3,4,5}
* 和数字3,由于3在这个数组中出现了4次,因此输出4.
*/
public class Demo53 {
public static void main(String[] args) {
Demo53 m1 = new Demo53();
int nums[] = new int[] {1,2,3,3,3,3,4,5};
int count = m1.getNumberOfK(nums,3);
System.out.println(count);
}
public int getNumberOfK(int[] nums,int k) {
int number = 0;
int len = nums.length;
if(nums!=null && len>0) {
int firstK = getFirstK(nums,k,0,len-1);
int lastK = getLastK(nums,k,0,len-1);
if(firstK>-1 && lastK>-1)
number = lastK-firstK+1;
}
return number;
}
//找到第一个k的位置
private int getFirstK(int[] nums,int k,int start,int end) {
if(start>end)
return -1;
int middleIndex = (start+end)/2;
int middleData = nums[middleIndex];
if(middleData==k) {
//找出第一个位置k的终止条件:
//如果中间数字的前面一个数字不是k,那么此时中间的数字刚好是第一个k
if((middleIndex>0 && nums[middleIndex-1]!=k)||
middleIndex==0) {
return middleIndex;
}else{//如果中间数字的前面一个数字也是k,那么第一个k肯定在数组的前半段
end = middleIndex-1;
}
}else if(middleData>k)
end = middleIndex-1;
else
start = middleIndex+1;
return getFirstK(nums,k,start,end);
}
//找出最后一个k的位置
private int getLastK(int nums[],int k,int start,int end) {
if(start > end)
return -1;
int middleIndex = (start+end)/2;
int middleData = nums[middleIndex];
if(middleData == k) {
//找出最后一个位置k的终止条件
if((middleIndex<nums.length-1 && nums[middleIndex+1]!=k)
|| middleIndex==nums.length-1 )
return middleIndex;
else //如果中间数字的下一个数字是k,在数组的后半段中去查找
start = middleIndex+1;
}else if(middleData<k) //如果中间数字比k小
start = middleIndex+1; //那么k只能出现在后半段
else //如果中间数字比k大
end = middleIndex-1; //那么k只能出现在前半段
return getLastK(nums,k,start,end);
}
}
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0-(n-1)之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
主要思路:
若数组没有缺失,则每个数字和它的下标都相等。然而,现在数组有缺失,说明从缺失的那个数开始,后面的数字都比它的下标大1。因此找出第一个数值和下标不相等的数,那么,它的下标就是缺失的那个数。
数组可看成两部分,前半段数值和下标是相等的,后半段从缺失项起数值和下标不相等,且数组是有序的。
所以,使用二分查找:
1.若中间元素的值和下标相等,那么下一轮查找只需要查找右半边;
2.如果中间元素的值和下标不相等:
a. 它的前一个元素和下标相等,意味着缺失的数是中间元素对应的下标
b. 前一个元素和下标也不相等,意味着下一轮查找我们只需要从左半边查找
时间复杂度:O(logn)
package jianZhiOffer;
/*
* 题目二:0~n-1中缺失的数字
* 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。
* 在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
*/
public class Demo5301 {
public static void main(String[] args) {
System.out.println(findMissingNumber(new int[]{0, 1, 2, 3, 4})); //5
System.out.println(findMissingNumber(new int[]{1, 2, 3, 4})); //0
System.out.println(findMissingNumber(new int[]{0, 1, 2, 4, 5})); //3
}
private static int findMissingNumber(int[] data) {
if (data == null || data.length <= 0)
return -1;
int left = 0;
int right = data.length - 1;
//值和下标相等的数在数组前半段
//值和下标不相等的数在数组后半段
while (left <= right) {
int middle = left + (right - left) / 2;
if (data[middle] != middle) {//值和下标不相等
//找到缺失的数
if (middle == 0 || data[middle - 1] == middle - 1) {
return middle;
} else {
//中间值和下标不相等并且前一个元素和下标也不相等,说明缺失在左半部分
right = middle - 1;
}
} else left = middle + 1; //中间值和下标相等,则需在后半段查找 缺失
}
//数组前面的数都和下标相等,说明缺失的是最大的那个数
if (left == data.length)
return data.length;
return -1;
}
}
假设一个单调递增的数组里每个元素都是整数并且是唯一的。
请编程实现一个函数,找出数组中任意一个数值等于其下标的元素。
例如:在数组{-3,-1,1,3,5}中,数字3和它的下标相等。
思路:
由于数组是单调递增排序的,尝试用二分查找算法进行优化
1.当前的数字的值和下标不相等:
a. 如果第i个数字的值大于i,那么它右边的数字都大于对应的下标,我们都可以忽略
b. 如果第i个数字的值小于i,那么它左边的所有数字的值都小于对应的下标,可以忽略
2.当前的数字的值和下标相等: 返回该数
package jianZhiOffer;
/*
* 题目三:数组中数值和下标相等的元素
* 假设一个单调递增的数组里每个元素都是整数并且是唯一的。
* 请编程实现一个函数,找出数组中任意一个数值等于其下标的元素。
* 例如:在数组{-3,-1,1,3,5}中,数字3和它的下标相等。
*/
public class Demo5302 {
public static void main(String[] args) {
int[] nums= {-3,-1,1,3,5};
int index = FindNumberSameAsIndex(nums);
System.out.println(index);
}
private static int FindNumberSameAsIndex(int[] numbers) {
if(numbers==null || numbers.length==0)
return -1;
int left = 0;
int right = numbers.length-1;
while(left<=right) {
int middle = left+((right-left)>>1);
if(numbers[middle]==middle)
return middle;
if(numbers[middle]>middle)
right = middle-1;
else
left = middle+1;
}
return -1;
}
}