目录
题目描述
数组中有一个数组出现的次数超过数组长度的一半,请找出这个数字。例如,输入一个长度为9的数组{1, 2, 3, 2, 2, 2, 5, 4, 2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
测试用例
- 功能测试(输入的数组中存在一个出现次数超过数组长度一半的数字;输入的数组中不存在一个出现次数超过数组长度一半的数字)
- 特殊输入测试(输入的数组中只有一个数字;输入空指针)
题目考点
- 考察应聘者对时间复杂度的理解。应聘者每想出一种解法,面试官都期待他能分析出这种解法的时间复杂度是多少。
- 考察应聘者思维的全面性。面试官除了要求应聘者能对有效的输入返回正确的结果,同时也期待应聘者能对无效的输入进行相应的处理。
解题思路
直接解法
先排序,如果存在出现次数大于数组长度一半的数,那么这个数必然在n/2位置上
排序的时间复杂度为O(nlogn)
HashMap解码
利用HashMap来统计数字出现的次数
时间复杂度不为O(1)
基于Partition函数的时间复杂度为O(n)的算法
当数组中有一个数字出现的次数超过了数组长度的一半,那么这个数字一定是数组的中位数。
我们利用Partition函数找到中位数,然后判断这个数出现的次数是否真的超过数组长度的一半,如果是就输出这个数,不然就不存在超过数组长度一半的数,返回0。
代码太长,这里就不写了,可以参考之前的快速排序的Partition算法。
这种方法不好的地方是会修改输入的数组。
根据数组特点找出时间复杂度为O(n)的算法
首先要明确一点:如果数组中一个数字出现的次数超过数组长度的一半,那么这个数字出现的次数比其他所有数字出现的次数之和还要多。
我们可以在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是该数组出现的次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为0,那么我们需要保存下一个数字,并把次数设为1,由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。
这其实是一个投票问题,见补充。
位运算解法
- 位运算: 时间复杂度o(n),但是感觉是必要不充分条件,比如[1,2,3,2,4,2,5,2,3]中没有主要元素(2出现4次,等于n/2=9/2=4),但是该方法会返回2,这是一个值得再改进的方法。具体怎么改进??遍历一次判断出现次数
public class Solution {
// 整型数组中的主要元素
// 位的特点
// 对32位的每一位,如果1为多数,则主要元素该位为1,否则为0.逐位构造主要元素
public int MoreThanHalfNum_Solution(int [] array) {
// 异常
if(array == null || array.length == 0){
return 0;
}
int res = 0, major = array.length/2;
for(int i = 31; i >= 0; --i){
int pos = 0;
for(int n: array){
pos += (n >> i) & 1; // 第i位为1的总数
}
// 多数为1则令该位为1,否则为0
pos = pos > major?1:0;
res |= pos << i;
}
if(checkMoreThanHalf(array, res)){
return res;
}else
return 0;
}
// ----- 功能函数 ---------------------------------------
public boolean checkMoreThanHalf(int[] array, int result){
boolean isMoreThanHalf = false;
int times = 0;
for(int i = 0 ; i < array.length; ++i){
if(array[i] == result){
times++;
}
}
if(times > (array.length >> 1)){
isMoreThanHalf = true;
}
return isMoreThanHalf;
}
}
参考解题
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if(array == null || array.length == 0){
return 0;
}
// 投票法,使用变量标记,代替删除数组中不同元素
int result = array[0];
int times = 1;
for(int i = 1; i < array.length; ++i){
if(times == 0){
result = array[i];
times = 1;
}
if(array[i] == result){
// 遍历元素和上次保存的结果一样,time++
times++;
}else{
times--;
}
}
if(checkMoreThanHalf(array, result)){
// 调用功能函数:检查数组和结果是否满足题目条件,即result出现次数超过数组长度一半
return result;
}else{
return 0;
}
}
// ----- 功能函数 ---------------------------------------
public boolean checkMoreThanHalf(int[] array, int result){
boolean isMoreThanHalf = false;
int times = 0;
for(int i = 0 ; i < array.length; ++i){
if(array[i] == result){
times++;
}
}
if(times > (array.length >> 1)){
isMoreThanHalf = true;
}
return isMoreThanHalf;
}
}
补充
关于寻找数组中的主要元素,什么是主要元素?就是指该数在数组中出现的次数大于n/2
,称为主要元素。怎么找?主流有三种方法
- 排序后找中位数: 时间复杂度o(nlogn)
- 位运算: 时间复杂度o(n),但是感觉是必要不充分条件,比如[1,2,3,2,4,2,5,2,3]中没有主要元素(2出现4次,等于n/2=9/2=4),但是该方法会返回2,这是一个值得再改进的方法。具体怎么改进??遍历一次判断出现次数
- 投票: 比较直观的解释:在数组中找到两个不相同的元素并删除它们,不断重复此过程,直到数组中元素都相同,那么剩下的元素就是主要元素。思想并不复杂,但是要凭空想出这个算法来也不是件容易的事。另外,给我们的是数组,直接在里面删除元素是很费时的。取而代之,可以利用一个计数变量来实现。