题目
数组中有一个数字出现的次数超过数组长度的一半,
请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。
由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
看到这个问题的时候我首先想到是:实例化一个新数组,用数组元素作为下标,元素出现的个数作为数组值,但是想想又发现不行;因为数组里的值不确定,因此实例化数组的时候数组大小就不确定,所以不能用这种方法。
接下来看一下解题思路:
思路一:
通过集合来解决;实例化一个Map集合,以数组元素为键,出现的次数为值,保存在Map里面,然后遍历Map,找到里面值最大的,判断是否大于数组大小的一半,若大于返回对应的键,否则返回0;
代码示例:
public int MoreThanHalfNum_Solution1(int [] array) {
if(array == null || array.length <= 0) {
return 0;
}
//键为数组元素,值为元素的个数
HashMap<Integer, Integer> map = new HashMap<>();
//用来统计数组中的元素个数
int count;
//遍历数组,按要求存放在map里
for (int i = 0; i < array.length; i++) {
if (map.containsKey(array[i])) { //如果已经存在map里,就将对应的值加1
count = map.get(array[i]);
count++;
map.put(array[i],count);
} else { //初始化,存入新值
count = 1;
map.put(array[i], count);
}
}
int max = 0; //用于保存重复元素的最大值
int res = 0; //用于保存重复元素最多的那个元素
for (int i: map.keySet()) {
if (max < map.get(i)) {
max = map.get(i);
res = i;
}
}
//若大于数组长度一半,返回该元素
return (max > (array.length / 2)) ? res : 0;
}
测试输出:
public static void main(String[] args) {
int[] array = {1,2,3,2,2,2,5,4,2};
MoreThanHalfNum mthn = new MoreThanHalfNum();
System.out.println(mthn.MoreThanHalfNum_Solution(array));
}
//输出结果
D:\LearnSoftware\Java\Java\jdk1.8.0_131\bin\java.exe "-
2
Process finished with exit code 0
总结
这里需要注意的是
- Map里的键不能重复,但是我这里每次往Map里存值的时候都会先把Map里的值取出做加加操作或初始化,所以Map里键重复新值替换旧值对于此算法并无影响;
- 这个算法的时间复杂度是O(n),需要遍历一遍数组和Map;
- 而且从Map里面找键对应的值的最大值还是比较麻烦的,如果是取键的最大值可以用TreeMap来替代HashMap,但是这里取的是值的最大值,所以就只能遍历键的集合了。
思路二
可以通过数组本身的性质来解这道题;先将数组进行排序,如果数组里重复数字个数大于数组大小的一半,那么这个重复数字一定位于数组中间;
代码示例:
public int MoreThanHalfNumSolution(int [] array) {
//检查数组的合法性
if (array == null || array.length <= 0) {
return 0;
}
//获取最后一个元素的下标
int length = array.length - 1;
//调用快排代码 O(nlogn)
quickSort(array,0, length);
//取排序后的中间元素
int result = array[length >> 1];
//检查重复元素个数是否超过数组长度的一半,O(n)
if(!checkMoreThanHakf(array, length, result)) {
return 0;
}
return result;
}
//递归调用,标杆分成的各个片段
private void quickSort(int[] array, int start, int end) {
int index;
if(start >= end) {
return;
}
index = partition(array, start, end);
quickSort(array, index + 1, end);
quickSort(array, start,index - 1);
}
//快排的核心
private int partition(int[] array, int start, int end) {
int pivot = array[start];
while (start < end) {
while (start < end && array[end] >= pivot) {
end--;
}
if (start < end) {
array[start] = array[end];
}
while (start < end && array[start] <= pivot) {
start++;
}
if (start < end) {
array[end] = array[start];
}
}
array[start] = pivot;
return start;
}
//检查重复元素个数是否超过数组长度的一半
private boolean checkMoreThanHakf(int[] array, int length, int result) {
int time = 0;
//根据排序后的中间元素记录和这个元素重复的个数
for (int i = 0; i <= length; i++) {
if(array[i] == result) {
time++;
}
}
boolean judge = true;
//判断是否大于数组元素个数的一半
if ((time << 1) < length + 1) {
judge = false;
}
return judge;
}
总结
正常情况下排序最快的时间复杂度是O(nlogn)-快排;这里通过快排将数组排好序,让后取中间的数,遍历一遍数组,找和这个元素相同的个数,再和数组的一半比较;
这里方法的时间复杂度说是O(n),但是我还不知道怎么推到出来的;
这里我用的是传统的快排(单轴快排),也可以用java.util.Arrays包下的排序,从JDK1.7后由单轴快排改为双轴快排。
参考:https://www.jianshu.com/p/2c6f79e8ce6e
思路三
题目要求为判断重复元素个数是否大于数组长度的一半,所以重复元素个数一定是数组里个数最多的;我们可以定义一个变量precursor用于保存前一个值,然后一个变量count用于记录次数;将数组的第一个元素赋值给precursor,然后遍历数组,若数组元素和precursor相等,count++;否则count–;当count减为0,就要更换precursor的值,这样最后保留下来的precursor值一定是数组里重复次数最多的,这时候只要再遍历一遍数组统计该值的次数再和数组长度的一半比较就可以了。
代码示例
public int MoreThanHalfNumSolution(int [] array) {
//判断数组的合法性
if (array == null || array.length <= 0) {
return 0;
}
//统计次数
int count = 1;
//记录前一个值
int precursor = array[0];
for (int i = 1; i < array.length; i++) {
if (precursor == array[i]) {
count++;
} else {
count--;
}
//当减为0,更换值
if (count == 0) {
precursor = array[i];
count = 1;
}
}
return calTime(array, precursor);
}
//判断找的那个值在数组里的个数是否大于数组大小的一半
private int calTime(int[] array, int value) {
int time = 0;
for (int i = 0; i < array.length; i++) {
if (value == array[i]) {
time++;
}
}
return (time > (array.length / 2)) ? value : 0;
}
总结
这的方法的时间复杂度为O(n);
注意 思路一和思路二需要修改原数组,二思路三不需要修改原数组。