剑指Offer面试题38:数字在排序数组中出现的次数 Java实现

42 篇文章 0 订阅
34 篇文章 0 订阅
题目:数字在排序数组中出现的次数
        统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,则输出次数4.
算法分析:
既然输入的数组是排序的,那么我们很自然的想到利用二分查找算法。在题目给出的例子中,我们可以先用二分查找算法找到第一个3.由于3可能出现多次,因此我们找到的3的左右两遍可能都是3,于是我们在找到3的左右两边顺序扫描,分别找出第一个3和最后一个3.因为要查找的数字在长度为n的数组中可能很出现O(n)次,所以顺序扫描的时间复杂度为O(n)。因此这种算法的效率和直接从头到尾顺序扫描整个数组统计3出现的次数的方法是一样的。显然,面试官是不会满意这种算法,它会提示我们还有更快的算法。
接下来我们思考如何更好的利用二分查找算法。假设我们统计数字k在排序数组中出现的次数。在前面的算法的时间主要消耗在如何确定重复出现的第一个k和最后一个k的位置上,有没有可以利用的二分查找算法直接找到第一个k和最后一个k。
我们先分析如何利用二分查找在数组中找到第一个k,二分查找算法总是先拿数组的中间的数字和k做比较。如果中间的数字比k大,那么k只能出现在数组的前半段,下一轮我们旨在数组的前半段查找就可以了。如果中间的数字比k小,那么k只能出现在数组的后半段,下一轮我们只在数组的后半段查找就可以了。如果中间的数字和k相等呢?我们先判断这个数字是不是第一个k。如果位于中间数字的前面一个数字不是k,此时中间的数字刚好就是第一个k。如果中间的数字的前面一个数字也是k,也就是说第一个k肯定在数组的前半段,下一轮我们仍然需要在数组的前半段查找。
同理我们利用上面的思路找到最后一个k。
找到第一个k和最后一个k后就可以知道k出现的次数了,

算法源程序:

/**************************************************************      
* Copyright (c) 2016, 
* All rights reserved.                   
* 版 本 号:v1.0                   
* 题目描述:数字在排序数组中出现的次数
*		       统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,则输出次数4.
* 输入描述:请输入一个升序数组(以空格隔开):
*			1 2 3 3 3 3 5 7
*			请输入要查找的数字K:
*			3
* 程序输出:查找到的数字3一共出现的次数是:4
* 问题分析: 无
* 算法描述:利用二分查找在数组中找到第一个k,二分查找算法总是先拿数组的中间的数字和k做比较。如果中间的数字比k大,
* 			那么k只能出现在数组的前半段,下一轮我们旨在数组的前半段查找就可以了。如果中间的数字比k小,
* 			那么k只能出现在数组的后半段,下一轮我们只在数组的后半段查找就可以了。如果中间的数字和k相等呢?
* 			我们先判断这个数字是不是第一个k。如果位于中间数字的前面一个数字不是k,此时中间的数字刚好就是第一个k。
* 			如果中间的数字的前面一个数字也是k,也就是说第一个k肯定在数组的前半段,下一轮我们仍然需要在数组的前半段查找。
*			同理我们利用上面的思路找到最后一个k。
*
* 完成日期:2016-09-24
***************************************************************/

package org.marsguo.offerproject38;

import java.util.Scanner;

class CountNumberOfK{
	private int getFirstK(int[] array,int length,int k,int start,int end){
		if(start > end)
			return -1;
		
		int middleIndex = (start + end)/2;
		int middleData = array[middleIndex];
		
		/*
		start始终为0,end不断前移或后移,直到找到第一个k
		*/
		if(middleData == k){					//中间数两侧都有要找的k
			if((middleIndex > 0 && array[middleIndex - 1] != k) || middleIndex ==0)
				return middleIndex;
			else{
				end = middleIndex - 1;
			}
		}
		else if(middleData > k){			//若中间数大于K,则要找的k在中间数前面,
			end = middleIndex - 1;			//把中间数前面一个数字赋给end,从这里开始找
		}
		else{								//要找的k在中间数右边
			start = middleIndex + 1;		
		}
		return getFirstK(array, length, k, start, end);
	}
	
	private int getLastK(int[] array,int length,int k ,int start,int end){
		if(start > end)
			return -1;
		
		int middleIndex = (start + end)/2;
		int middleData = array[middleIndex];
		
		/*
		star不断前移或后移,直到找到第一个k
		*/
		if(middleData == k){
			if((middleIndex < length - 1 && array[middleIndex + 1] != k)||
					middleIndex == length - 1){
				return middleIndex;
			}
			else{
				start = middleIndex + 1;
			}
		}else if(middleData < k)
			start = middleIndex + 1;
		else
			end = middleIndex - 1;
		return getLastK(array, length, k, start, end);
	}
	
	public int getNumberOfK(int[] array,int length,int k){
		int number = 0;
		
		if(array != null && length > 0){
			/*
			first为第一个K所在位置,last为最后一个k所在位置
			用last-first + 1即为k的个数
			*/
			int first = getFirstK(array, length, k, 0, length - 1);
			int last = getLastK(array, length, k, 0, length - 1);
			
			if(first > -1 && last > -1)
				number = last - first + 1;
		}
		
		return number;
	}
}


public class CountNumberMethod1 {
	public static void main(String[] args){
		Scanner scanner = new Scanner(System.in);
		System.out.println("请输入一个升序数组(以空格隔开):");
		String str = scanner.nextLine();
		System.out.println("请输入要查找的数字K:");
		int k = scanner.nextInt();
		scanner.close();
		
		String[] temp = str.split(" ");
		int[] numarray = new int[temp.length];
		for(int i = 0; i < temp.length; i++){
			numarray[i] = Integer.parseInt(temp[i]);
		}
		
		CountNumberOfK countnumberofk = new CountNumberOfK();
		System.out.println("查找到的数字" + k + "一共出现的次数是:");
		System.out.println(countnumberofk.getNumberOfK(numarray, numarray.length, k)); 
	}
}

程序运行结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值