剑指Offer+第29题+数组中出现次数超过一半的数字+java

本文介绍了一种在数组中寻找出现次数超过数组长度一半的数字的算法。通过两种O(n)复杂度的方法,一种基于快速排序的Partition函数,另一种通过遍历数组计数,有效地解决了这一问题。

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现5次,超过数组长度的一半,因此输出2.

思路1:

解法一:基于Partition函数的O(n)算法:

我们的算法是受快速排序的算法的启发。在随机快速排序的算法中,我们先在数组中随机的选择一个数字,然后调数组中数字的顺序,使得比选中的数字小数字排在它的左边,比选中的数字大的数字都排在它的右边。比如这个选中的数字的下标刚好是n/2,那么这个数字就是数组中的中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在它的左边部分的数组中查找。如果它的下标小于n/2,那么中位数应该在它的右边,我们可以接着在它的右边部分中查找。这是一个典型的递归过程。

先用快排把数组排好序,然后中间的那个数就是出现次数最多的,快排复杂度为O(nlogn)。但实际中,我们不用一次性完成快排,只需快排一轮后检查我们选择的快排的key所在的下标是不是数组中间数就行了。总复杂度O(n),这种方法会修改数组,因此要看是否允许这样做,快排是不稳定的排序。

思路2:

复杂度也是O(n)且不需修改数组,方法是根据数组特点:定义一个记录出现次数的变量并初始为1,然后从头开始遍历,如果该数与上一个数不同,次数减一,反之加一,如果次数为0,保存下一个数为要返回的数,并把次数设为1。因为要找的数字比其他数字总数多,所以要找到数字就是最后一次把次数设为1时对应的数字。
 

代码:

package array;

public class Offer29 {
	
	//选用partition函数
	public int partition(int [] arr, int left, int right){
		int result = arr[left];
		if(left>right)
			return -1;
		while(left<right){
			while(left<right && arr[right] >= result)
				right--;
			arr[left] = arr[right];
			while(left<right && arr[left] < result)
				left++;
			arr[right] = arr[left];
		}
		arr[left] = result;
		return left;
	}
	
	public int moreThanHalfNum(int [] arr){
		if(arr == null ||arr.length == 0)
			return -1;
		int length = arr.length;
		int middle = length >>1;
		int start = 0;
		int end = length-1;
		int index = partition(arr, start, end);
		while(index != middle){
			if(index > middle){
				end = index-1;
				index = partition(arr, start, end);
			}else{
				start = index+1;
				index = partition(arr, start, end);
			}
		}
		
		int result = arr[middle];
		if(!checkMoreThanHalf(arr, result))
			result = -1;
		
		return result;
	}
	
	public boolean checkMoreThanHalf(int[] arr, int result) {
		int times = 0;
		for(int i = 0;i<arr.length;i++){
			if(arr[i] == result)
				times++;
		}
		boolean isMoreThanHalf = true;
		if(times*2<=arr.length)
			isMoreThanHalf = false;
		
		return isMoreThanHalf;
	}
	
	//方法2
	public int moreThanHalfNum_2(int [] arr){
		if(arr == null || arr.length == 0)
			return -1;
		int result = arr[0];
		int times = 1;
		for(int i = 0;i<arr.length;i++){
			if(times == 0){
				result = arr[i];
				times = 1;
			}else if(arr[i] == result)
				times++;
			else
				times--;
		}
		if(!checkMoreThanHalf(arr, result))
			result = -1;
		
		return result;
	}

	public static void main(String[] args) {
		
		Offer29 of29 = new Offer29();
		
		//功能测试,1,输入的数组中存在一个出现次数超过数组长度一般的数字
		int [] arr11 = {1,2,3,2,2,2,5,4,2};
		//System.out.println(of29.moreThanHalfNum(arr11));
		System.out.println(of29.moreThanHalfNum_2(arr11));
		
		//功能测试,2,输入的数组中不存在一个出现次数超过数组长度一般的数字
		int [] arr21 = {1,2,3,2,1,3,5,4,2};
		//System.out.println(of29.moreThanHalfNum(arr21));
		System.out.println(of29.moreThanHalfNum_2(arr21));
		
		//特殊输入测试,3,输入的数组中只有一个数字
		int [] arr31 = {1};
		//System.out.println(of29.moreThanHalfNum(arr31));
		System.out.println(of29.moreThanHalfNum_2(arr31));
		
		//特殊输入测试,3,输入NULL指针
		int [] arr41 = null;
		//System.out.println(of29.moreThanHalfNum(arr41));
		System.out.println(of29.moreThanHalfNum_2(arr41));

	}

}

运行结果:

2
-1
1
-1

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值