剑指Offer-53:数字在排序数组中出现的次数

题目

统计一个数字在排序数组中出现的次数。例如输入排序数组{1, 2, 3, 3,3, 3, 4, 5}和数字3,由于3在这个数组中出现了4次,因此输出4。

思路

代码1:

public int GetNumberOfK(int [] array , int k) {
    	if(array.length == 0)
    		return 0;
    	int between =  GetNumberOfKCore(array, 0, array.length-1, k);
    	if(between == -1)
    		return 0;
    	int ret = 1;
    	int begin = between-1, end = between+1; 
    	while(begin>=0 && array[begin] == k) {
    		ret++;
    		begin--;
    	}
    	while(end<array.length && array[end] == k) {
    		ret++;
    		end++;
    	}
    	return ret;
    }
    public int GetNumberOfKCore(int [] array , int begin, int end, int k) {
    	int len = end - begin + 1;
    	int mid = begin + len / 2;
    	while(begin <= end) {
    		if(array[mid] > k) {
    			return GetNumberOfKCore(array , begin, mid-1, k);
    		}
    		if(array[mid] < k) {
    			return GetNumberOfKCore(array , mid+1, end, k);
    		}
    		if(array[mid] == k) {
    			return mid;
    		}
    	}
    	//没有找到
    	return -1;
 }

分析:对于例子来说,如果采用二分法找到某一个3后,再往前遍历和往后遍历到第一个和最后一个3,在长度为n的数组中有可能出现O(n)个3,因此这样的扫描方法时间复杂度为O(n),效率与从头到尾扫描一样,速度太慢。

这题关键是找到第一个和最后一个3,因此我们尝试改进二分法:中间数字比3大或者小的情况与之前类似,关键是中间数字等于3的情况,这时可以分类讨论如下:

1)如果中间数字的前一个数字也等于3,说明第一个3在前面,继续在前半段查找第一个3;

2)如果中间数字的前一个数字不等于3,说明该位置是第一个3;

3)如果中间数字的后一个数字也等于3,说明最后一个3在后面,继续在后半段查找最后一个3;

2)如果中间数字的后一个数字不等于3,说明该位置是最后一个3;

代码2:

public class NumberOfK {
    public int GetNumberOfK(int [] array , int k) {
        if(array==null || array.length<=0)
            return 0;
         int firstK = getFirstK(array,0,array.length-1,k);
         if(firstK == -1)
             return 0;
         int lastK = getLastK(array,firstK,array.length-1,k);
         return lastK-firstK+1;
     }
       
     private int getFirstK(int[] arr, int start, int end,int k){
         if(start>end)
             return -1;
         int mid = (start+end)>>1;
         if(arr[mid]==k){
             if( mid == 0 ||arr[mid-1]!=k )
                 return mid;
             else
                 end = mid-1;
         }else if(arr[mid]<k){
             start = mid+1;
         }else{
             end = mid-1;
         }
         return getFirstK(arr,start,end,k);
     }
       
     private int getLastK(int[] arr, int start, int end,int k){
         if(start>end)
             return -1;
         int mid = (start+end)>>1;
         if(arr[mid]==k){
             if(mid==arr.length-1 || arr[mid+1]!=k )
                 return mid;
             else
                 start = mid+1;
         }else if(arr[mid]<k){
             start = mid+1;
         }else{
             end = mid-1;
         }
         return getLastK(arr,start,end,k);
     }
}

代码3:
除此之外,还有另一种奇妙的思路,因为data中都是整数,所以我们不用搜索k的两个位置,而是直接搜索k-0.5和k+0.5这两个数应该插入的位置,然后相减即可。

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        return biSearch(array, k+0.5) - biSearch(array, k-0.5);
    }
    public int biSearch(int [] array, double k){
        int start  = 0, end = array.length - 1;
        while(start <= end){
            int mid = start + (end - start)/2;
            if(array[mid] > k){
                end = mid - 1;
            }else{
                start = mid + 1;
            }
        }
        return start;
    }
}

题53(二):
题目: 0到n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0到n-1之内。在范围0到n-1的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

主要思路:若数组没有缺失,则每个数字和它的下标都相等。然而,现在数组有缺失,说明从缺失的那个数开始,后面的数字都比它的下标大1。因此找出第一个数值和下标不相等的数,那么,它的下标就是缺失的那个数。
  数组可看成两部分,前半段数值和下标相等,后半段数值和下标不相等,且数组是有序的。所以,使用二分查找,若中间数的值和下标相等,则在后半段继续寻找;若中间数的值和下标不相等,且中间数的前一个元素的值等于它的下标相等,那么这个中间数就是第一个值和下标不相等的数,它的下标就是缺失的那个数,否则继续在前半段寻找。

 //二分查找
    public int findMissingNumber(int[] data) {
    	if(data.length <= 1)
    		return -1;
    	return findMissingNumberCore(data, 0, data.length-1);			
    }
    public int findMissingNumberCore(int[] data, int begin, int end) {
    	while(begin <= end) {
    		int mid = (end - begin)/2 + begin;
    		if(data[mid] == mid) {
    			begin = mid + 1;
    		}else if(data[mid] > mid) {
    			if(mid-1>=0 && data[mid-1]==mid-1)
    				return mid;
    			end = mid-1;
    		}
    	}
    	return -1;
}

题53(三):
题目: 数组中数值和下标相等的元素
假设一个单调递增的数组里的每个元素都是整数并且是唯一的。请编程实现一个函数找出数组中任意一个数值等于其下标的元素。例如,在数组{-3, -1, 1, 3, 5}中,数字3和它的下标相等。

主要思路:数组是递增有序的,使用二分查找。先获取数组的中间数,若中间数的值和下标相等,则找到一个满足条件的数;若中间数的值大于它的下标,则根据数组递增的特性可知:中间数以后的元素的值至少每个比前面大1,同时它们的下标每次也是增加1,从而右边的这些元素的值都大于它们的下标,因此需要继续在左边寻找。同理,若中间数的值小于它的下标,则中间数左边的那些元素的值也都小于它们的下标,因此需要继续在右边寻找。

public class IntegerIdenticalToIndex {
    public int getNumberSameAsIndex(int[] arr) {
        if(arr==null || arr.length<=0)
            return -1;  //代表错误
        int low=0;
        int high=arr.length-1;
        while(low<=high) {
            int mid= (high+low)>>1;
            if(arr[mid]>mid)
                high=mid-1;
            else if(arr[mid]<mid)
                low=mid+1;
            else
                return mid;
        }
        return -1;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_32534441/article/details/89217457
今日推荐