题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路一:利用map统计每个数字出现的次数
代码
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
int num = 0;
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
int len = array.length ;
if(len == 0) return 0;
for(int i = 0; i < len ;i++){
if(map.containsKey(array[i])){
int count = map.get(array[i]);
++count;
map.put(array[i],count);
}else{
map.put(array[i],1);
}
}
for(Map.Entry<Integer,Integer> entry: map.entrySet()){
if(entry.getValue() > (len/2)){
num = entry.getKey();
}
}
return num;
}
}
总结
- map的遍历方式
- 使用map存储出现次数的映射关系
- map.put(key,value)
思路二:先对数组进行排序,然后取出arr[n/2]
由于出现次数超过一半,那么最中间的数一定是那个出现次数超过一半的数,也就是中位数
我们先用排序算法对该数组排序,若用快速选择排序,时间复杂度为O(nlogn)
思路三:基于partition函数的O(n)的算法
我们知道快速排序中,每次划分都是基于partition函数返回的index作为划分点,使得数组左边的数小于arr[index],数组右边的数大于arr[index]
这里其实partition返回的index也就是数组arr中第k大数据的坐标索引
所以借鉴过来,本题需要求出现次数超过一半的数,那么实际上也就是中位数,即索引为n/2的数,
就等同于求第n/2大的数据
快速排序算法中我们是通过partition函数求出索引,本题我们是知道了索引,求具体的数据
那么我们就需要以n/2为基准,使得左边的数小于arr[n/2],右边的数大于arr[n/2]
实现
public class Solution {
//检测无效输入,如果数字的次数不超过长度的一半
public boolean isValid(int[] arr,int numer){
int count = 0;
for(int i = 0; i < arr.length; i++){
if(numer == arr[i]) count++;
}
if(count * 2 <= arr.length) return false;
return true;
}
public void swap(int[] arr,int i,int j){
int temp = 0;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public int partition(int[] arr,int low,int high){
int base = arr[high];
int i = low - 1;
for(int j =low; j < high; j++){
//注意这里是小于等于
if(arr[j] <= base){
i++;
swap(arr,i,j);
}
}
swap(arr,i + 1,high);
return i+1;
}
public int MoreThanHalfNum_Solution(int [] array) {
if(array == null || array.length <= 0)return 0;
int index = partition(array, 0, array.length - 1);
int middle = (array.length - 1) >> 1;
while (index != middle){
if(index < middle){
index = partition(array, index + 1, array.length - 1);
}else {
index = partition(array, 0, index - 1);
}
}
boolean flg = isValid(array, array[middle]);
if(!flg) return 0;
return array[middle];
}
}
该思路的时间复杂度:平均时间复杂度为O(n * logn/2) = O(nlogn),如果一次性就能找到n/2的下标,那么时间复杂度可以达到o(n)
但是该算法的缺点在于修改了原数组
思路四:根据数组特点找出O(n)的算法
由于有个数字出现的次数超过数组长度的一半,那么也就是说它出现的次数比其他数字出现的次数之和还要多,如果我们将数组中的数字两两同或(相同为1,相异为0)那么两两抵消之后,最后只会剩下这个出现出现次数超过一半的数字,那么我们如何实现这个同或抵消的过程呢
首先我们需要保存当前正在读取的数,还有当前数字出现的次数,如果下一个数字和当前数字不相同,那么当前数字的次数就减1,
public class Solution {
public boolean isValid(int[] arr,int numer){
int count = 0;
for(int i = 0; i < arr.length; i++){
if(numer == arr[i]) count++;
}
if(count * 2 <= arr.length) return false;
return true;
}
public int MoreThanHalfNum_Solution(int [] array) {
if(array == null || array.length <= 0) return 0;
int cur = array[0];
int count = 1;
int res = array[0];//初始化为array[0]有可能第一个数就是返回值
for(int i = 1; i < array.length; i++){
if(cur != array[i] ){
if(count != 0){
count--;
}else {
count = 1;
res = array[i];
}
cur = array[i];
}else {
count++;
}
}
boolean flg = isValid(array, res);
if(!flg) return 0;
return res;
}
}
该算法的时间复杂度为:O(n)
参考
《剑指offer纪念版》