《剑指Offer》Java刷题 NO.28 数组中出现次数超过一半的数字(数组、哈希表、在线处理、动态规划)
传送门:《剑指Offer刷题总目录》
时间:2020-05-07
一个多月没刷题了,这一个月浑浑噩噩,说是一边在做项目一边在看书,但是效率有多低只有自己清楚,没有目标意识就只能过一天算一天,并且每天都消逝的好快,给自己定个目标,每天至少三道题!冲!
题目:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路:
法一:O(n),O(n)
自己想的是可以用HashMap,key值用来保存数组元素,val值为该元素在数组出现的次数,然后遍历一遍找出个数超过数组长度一半的数字,没有则输出0;
这样时间复杂度为O(n),空间换时间
法二:在线处理算法O(n),O(1)【基于数组特点】
“消解法”首先要想到——如果数组中存在一个数字出现次数超过一半,那它比其他所有数字出现次数之和还要多。如果以这个数字与一个不同的数字配对从数组里两两删除的话,最后剩下的数字如果只有一个,那就是这个数字;如果剩下两个以上且相同,那也只能是这个数字。
将起始次数times设置为1,起始数字result设置为第一个数。不断向右推进,若当前次数为0(说明刚好消解完),将result更新为当前数字(重新开始,times设置为1)。若当前数字与result相同,次数+1;若不同则-1(消解),接下来只要确认result出现的次数是否超过一半即可。
法三:Partation算法 O(n),O(1)
稍微有点麻烦,但主要是思想
要找的数在排好序之后肯定是中位数;反过来不成立(所以最后需要看出现次数)
但是这个地方并不需要真的去排序,只需要中间那个数的左边都比它小,右边都比它大即可,于是利用快速排序中用到的Partation算法,每次随机产生一个坐标将其作为pivot,执行一次partation算法之后返回pivot的坐标,如果不是刚好在中间,就再往两边找;因为每次都是随机产生位置,所以最好情况下时间复杂度为T(N)=T(N/2)+N也就是O(N)
详见注释
法二、三缺点: 需要判断是否真的次数超过一半,因为上述条件是必要条件但不是充分条件,时间效率没有hash表法好
Java代码:
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName MoreThanHalfNumSolutio
* @Discription 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
* 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,
* 因此输出2。如果不存在则输出0。
* @Author lemon
* @Date 2020/5/7 10:14
**/
public class MoreThanHalfNumSolution {
/**
* 哈希表法,key存放数组元素,val存放出现次数
*/
public static int moreThanHalfNumSolutionOne(int [] array) {
if(array.length <= 0){
return 0;
}
int result = 0;
Map<Integer,Integer> temp = new HashMap<>(array.length);
for(int i = 0;i < array.length;i++){
if(temp.get(array[i]) == null){
//未出现过就设置val值为1
temp.put(array[i],1);
}
else {
//出现过就设置val值加一
temp.put(array[i],temp.get(array[i]) + 1);
}
System.out.println(temp.get(array[i]));
if(temp.get(array[i]) > array.length/2){
//放完以后顺便判断一下是否出现次数大于数组长度的一半
result = array[i];
break;
}
}
return result;
}
/**
*消解法:初始times值设置为1,result设置为array[0],每遇到一个相同的就times++,遇到不同的就times--,当times==0时,
* 证明刚好消解完,重新开始,更新result为当前元素,times为1,直到遍历完所有元素;那么result是消解完之后剩下的元素
* 接下来判断其出现次数是否大于数组长度的一半就好了
*/
public static int moreThanHalfNumSolutionTwo(int [] array){
int times = 1;
int result = array[0];
for(int i = 1;i < array.length;i++){
if(times == 0){
//刚好消解完,重新开始
times = 1;
result = array[i];
}
if(result == array[i]){
times++;
}
else {times--; }
}
times = 0;
//判断result出现次数是否超过数组长度一半
for(int i = 0;i < array.length;i++){
if(array[i] == result){
times++;
}
}
return times > array.length/2 ? result:0;
}
/**
* 要找的数在排好序之后肯定是中位数;反过来不成立(所以最后需要看出现次数)
* partation算法:执行一次之后左边都比pivot小,右边都比它大
* 在数组中随机选择一个位置上的元素作为pivot,先藏在最后,然后设置左右两个指针(坐标)。
* 开始高指针向左移动,如果遇到小于等于中间值的数据,先停下来;切换低指针移动,当低指针
* 移动到大于等于中间值的时候,高低指针数据互换,重复以上操作
* 直到左指针等于右指针,此时都指在较大元素处,将之和pivot互换
*/
public int moreThanHalfNumSolutionThree(int [] array){
if(array.length == 0 ){
return 0;
}
int start = 0;
int end = array.length - 1;
int middle = array.length >> 1;
int index = partation(array,start,end);
//刚好在中间就返回该元素,在中间的左边就往右边找,否则往左边找
while(index != middle){
if(index > middle){
end = index - 1;
index = partation(array,start,end);
}
else if(index < middle){
start = index + 1;
index = partation(array,index + 1,end);
}
}
int result = array[middle];
//中位数不一定会出现次数大于数组一半长度
int times = 0;
for(int i = 0;i < array.length;i++){
if(array[i] == result){
times++;
}
}
return times > middle ? result:0;
}
public void swap(int[] arr,int a,int b){
int temp;
temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public int partation(int[] arr,int start,int end){
//产生一个[left,right]范围的随机数,Math.random()是随机选取>= 0.0 且< 1.0 的伪随机 double 值
int pivotIndex = (int)(start + Math.random()*(end - start + 1));
//pivot藏到最后
swap(arr,pivotIndex,end);
int pivot = arr[end];
//定义两个指针
int left = start - 1;
int right = end;
while(true){
//相等时也停下来做交换,这样极端情况数组全是相同元素时能使得每次pivot处于中间位置
//每次递归长度减半,否则只是N,N-1,N-2...
while(left < right && arr[++left] < pivot){}
while(left < right && arr[--right] > pivot){}
if(left < right){
swap(arr,left,right);
}
else break;
}
//最后肯定是left=right并且指向较大的数,将pivot与之互换
swap(arr,right,end);
//返回此时pivot的坐标
return right;
}
public static void main(String[] args) {
int[] a = {4,2,1,4,2,4};
MoreThanHalfNumSolution moreThanHalfNumSolution = new MoreThanHalfNumSolution();
System.out.println(moreThanHalfNumSolution.moreThanHalfNumSolutionThree(a));
}
}