题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
题目抽象:给定一个数组,找出数组中的众数,若有,返回众数,若没有,返回0
众数定义:数组中出现次数大于数组一般的元素
前两种方法最容易实现,笔试或面试可以先做出来,然后根据面试要求不断优化
方法一:哈希法
map/unordered_map原理和使用整理: https://blog.youkuaiyun.com/Blues1021/article/details/45054159?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
根据题目意思,显然可以先遍历一遍数组,在map中存每个元素出现的次数,然后再遍历一次数组,找出众数。
代码
#include<unordered_map>
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()<=0){//异常处理
return 0;
}
unordered_map<int,int> map;
for(const int val:numbers) ++map[val];//统计每个元素出现次数
for(const int val:numbers){
if(map[val]>numbers.size()/2){//遍历找到val次数大于n/2的就是要找的那个众数
return val;
}
}
return 0;
}
};
时间复杂度:O(n)
空间复杂度:O(n)
方法二:排序法
可以先将数组排序,然后可能的众数肯定在数组中间,然后判断一下。
代码
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()<=0){//异常处理
return 0;
}
sort(numbers.begin(),numbers.end());//对数组排序
int cond=numbers[numbers.size()/2];//取排序好数组的中间元素
int cnt=0;
for(const int k:numbers){
if(cond==k) ++cnt;//统计该元素出现的次数
}
if(cnt>numbers.size()/2) return cond;
return 0;
}
};
时间复杂度:O(nlongn)
空间复杂度:O(1)
如果时间复杂度要求为O(n),考虑下面的方法
方法三:基于Partiotion函数,但是这种方法会改变数组,时间复杂度为O(N)
class Solution {
public:
bool checkHalf(vector<int> num,int length,int tar){
int count=0;
for(int i=0;i<length;++i){
if(num[i]==tar){
count++;
}
}
bool isMoreHalf=true;
if(2*count<=length){//次数最多的数的个数必须大于数组长度的一半
isMoreHalf=false;
}
return isMoreHalf;
}
int Partition(vector<int> &num,int left,int right){//快速排序的基础,选定一个数,实现在数组中比它小的数在它左边,比它大的数在它右边
int pivot=num[right];
int small=left-1;
for(int j=left;j<right;++j){
if(num[j]<=pivot){//将小于pivot的数向前交换
++small;
swap(num[small], num[j]);
}
}
swap(num[small+1],num[right]);
return small+1;
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()<=0){//异常处理
return 0;
}
int left=0,right=numbers.size()-1;
int index=Partition(numbers,left,right);
int mid=numbers.size()>>1;
while(index!=mid){//如果选定的数下标刚好是数组一半,则这个数就是数组的中位数
if(index>mid){//下标大于n/2,中位数应该在他的左边
right=index-1;
index=Partition(numbers,left,right);
}else{//下标小于n/2,中位数应该在他的右边
left=index+1;
index=Partition(numbers,left,right);
}
}
int result=numbers[mid];
if(!checkHalf(numbers,numbers.size(),result)){
result=0;
}
return result;
}
};
方法四:基于数组特点,这种方法不改变数组结构,时间复杂度为O(N)
遍历数组的时候存储两个值,一个是数组中的数,一个是次数,当我们遍历到下一个数字的时候,如果和之前保存的数字相同,次数加1,如果不同次数减1,如果次数为0,则保存下一个数字,并把次数设为1,由于要找的数字比其他所有数字出现的次数之和还要多,因此最后以此次数设为1的数字就是目标。
class Solution {
public:
bool checkHalf(vector<int> num,int length,int tar){
int count=0;
for(int i=0;i<length;++i){
if(num[i]==tar){
count++;
}
}
bool isMoreHalf=true;
if(2*count<=length){//次数最多的数的个数必须大于数组长度的一半
isMoreHalf=false;
}
return isMoreHalf;
}
int Partition(vector<int> &num,int left,int right){//快速排序的基础,选定一个数,实现在数组中比它小的数在它左边,比它大的数在它右边
int pivot=num[right];
int small=left-1;
for(int j=left;j<right;++j){
if(num[j]<=pivot){//将小于pivot的数向前交换
++small;
swap(num[small], num[j]);
}
}
swap(num[small+1],num[right]);
return small+1;
}
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()<=0){//异常处理
return 0;
}
int result=numbers[0];
int times=1;
for(int i=1;i<numbers.size();++i){
if(times==0){
result=numbers[i];
times=1;
}else if(numbers[i]==result){
++times;
}else{
--times;
}
}
if(!checkHalf(numbers,numbers.size(),result)){
result=0;
}
return result;
}
};