《剑指Offer》面试题:超过数组长度的一半的数

2024-05-14 05:08

本文主要是介绍《剑指Offer》面试题:超过数组长度的一半的数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目描述:

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

思路
解决此题的思路有很多。
1、最容易想到的方法:将数组进行排序,取中位数即可。但是时间复杂度为O(nlogn)

2、考虑用哈希,key保存数组元素,value保存出现的次数,这样在遍历O(n)能做出key-value的映射,再用O(k)(k为需要的槽的个数)可以找出出现次数超过一半的key,但是由于数组中元素的大小范围未知,因此使用这种方法,首先不能确定哈希表的大小,即使通过遍历一次求得了最大值,范围很大的话,又要花费很大心思设计很好的哈希函数来完成key-value的映射,且不具有通用性,而且还要考虑数组中元素为负值的情况,因此用哈希表不合适。如果说数字只有0-9的话可以考虑设计一个Hash table,遍历一次就能知道每个数字出现的次数。

3、思路1中需要将数组进行排序,而事实上可以不用对数组进行排序,
或者说仅部分排序,受快速排序的partition函数的启发,
我们可以利用反复调用partition函数来求的该数字。我们现在数组中随机选取一个数字,
而后通过Partition函数返回该数字在数组中的索引index,如果index刚好等于n/2,则这个数字便是数组的中位数,也即是要求的数,
如果index大于n/2,则中位数肯定在index的左边,在左边继续寻找即可,反之在右边寻找。这样可以只在index的一边寻找,
而不用两边都排序,减少了一半排序时间。这种情况的平均时间复杂度大致为:T(n) = n+n/2+n/4+n/8+….+1,很明显当n很大时,T(n)趋近于2n,
也就是说平均情况下时间复杂度为O(n),但是这种情况下,最坏的时间复杂度依然为O(n*n),最坏情况下,index总是位于数组的最左或最右边,
这样时间复杂度为T(n) = n+n-1+n-2+n-3+….+1 = n(n-1)/2,显然,时间复杂度为O(n*n),空间复杂度为O(1)。

思路3的实现代码如下


/*
题目描述:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。输入:
每个测试案例包括2行:第一行输入一个整数n(1<=n<=100000),表示数组中元素的个数。第二行输入n个整数,表示数组中的每个元素,这n个整数的范围是[1,1000000000]。输出:
对应每个测试案例,输出出现的次数超过数组长度的一半的数,如果没有输出-1。样例输入:
9
1 2 3 2 2 2 5 4 2
样例输出:
2
*/ #include<stdio.h>
#include<stdlib.h>//定义一个全局变量,用来指示输入的数据中是否有效。这里的“有效”指的是:输入的数据中次数超过一半的数
bool isValid=false; 
/*
函数功能:判断在数组arr中result出现的次数是否为arr长度的一半以上。 
参数的说明
@param arr:数组的指针
@param len:数组的长度
@param result:待检测的数 
*/
bool isMoreThanHalfInArray(int *arr,int n,int result){if(arr==NULL||n<=0){return false;}int times=0;for(int i=0;i<n;i++){if(arr[i]==result){times++;}}bool isHalf=false;if(times*2>=n){isHalf=true;}return isHalf;}
void swap(int *a,int *b){if(a!=NULL&&b!=NULL){int temp=*a;*a=*b;*b=temp;}
}
int partition(int *arr,int begin,int end){if(arr==NULL||begin<0||end<0||begin>end){return -1;}int index=begin;//选取首元素为主元for(int i=begin;i<=end;i++) {if(arr[i]<arr[begin]){index++;swap(&arr[index],&arr[i]);}}swap(&arr[begin],&arr[index]);//将主元交换到他应该在的位置return index; }int findMoreThanHalfNumInArray(int *arr,int n){if(arr==NULL||n<=0){//输入数据无效isValid=false; return -1;}int begin=0;int end=n-1;int mid=n>>1;int index=partition(arr,begin,end);//利用快排得到主元的索引while(index!=mid) {if(index>mid){//说明mid在index的左边end=index-1;index=partition(arr,begin,end); }else{begin=index+1;index=partition(arr,begin,end);}}//上面的while循环就找到的中位数,但是在返回之前,还是需要判断此种位数的出现的次数是否达到了数组长度的一半以上。若达到了,则返回就可以了bool isMoreThanHalf=isMoreThanHalfInArray(arr,n,arr[index]);if(!isMoreThanHalf){isValid=false;return -1;} return arr[index]; }
int main(void){int n;while(scanf("%d",&n)!=EOF&&n>0){int *arr=(int *)malloc(n*sizeof(int));if(arr==NULL)exit(EXIT_FAILURE);for(int i=0;i<n;i++){int val;scanf("%d",&val);arr[i]=val;}isValid=true;int result=findMoreThanHalfNumInArray(arr,n);if(isValid){printf("%d",result);}else{printf("-1");}printf("\n");}return 0;
}

4、出现的次数超过数组长度的一半,表明这个数字出现的次数比其他数字出现的次数的总和还多。所以我们可以考虑每次删除两个不同的数,那么在剩下的数中,出现的次数仍然超过总数的一半。
通过不断重复这个过程,不断排除掉其它的数,最终找到那个出现次数超过一半的数字。
这个方法,免去了上述思路一的排序,也避免了思路2中空间O(N)的开销,总得说来,时间复杂度只有O(N),空间复杂度为O(1),不失为最佳方法。
例:数组 a[5]={0,1,2,1,1};
我们要查找的数字为1,操作步骤为:遍历整个数组,然后每次删除不同的两个数字,过程如下:
0 1 2 1 1 =>2 1 1=>1

具体实现:我们在考虑删除两个不同的数字的时候,实际上可以通过计数来实现,而不是物理上真正的删除。
在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,
如果下一个数字和我们之前保存的数字相同,则次数加1。如果下一个数字和我们之前保存的数字不同,则次数减1。
如果次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,因此,最后一次把次数设定为1的数可能是我们要寻找的数,但不是一定。

注意:那么要找的数字肯定是最后一次把次数设为1时对应的数字,网上都是这么说,但此句话欠妥,例如:数组为1、3、2;则最后一次把次数设为1的数字为2,但数组中并没有次数多于一般的数。因此,最后还需要判断。

下面的实现代码是按照思路4来实现的。

/*
测试函数的要求如下输入:
每个测试案例包括2行:第一行输入一个整数n(1<=n<=100000),表示数组中元素的个数。第二行输入n个整数,表示数组中的每个元素,这n个整数的范围是[1,1000000000]。输出:
对应每个测试案例,输出出现的次数超过数组长度的一半的数,如果没有输出-1。样例输入:
9
1 2 3 2 2 2 5 4 2
样例输出:
2
*/ #include<stdio.h>
#include<stdlib.h>bool isExist; //定义一个全局变量,用来标识数组中是否存在超过一半的数。 当为false时,表示,不存在并输出-1.也是为了区分isExist=true,而超过一半的数为-1的情况。 
/*
函数功能:判断在数组arr中result出现的次数是否为arr长度的一半以上。 
参数的说明
@param arr:数组的指针
@param len:数组的长度
@param result:待检测的数 
*/
bool isMoreThanHalfNumInArray(int *arr,int len,int result){if(arr==NULL||len<=0){isExist=false;return false;}int times=0;for(int i=0;i<len;i++){if(arr[i]==result){times++;}}bool isMoreThanHalf=false; if(times*2>=len){isMoreThanHalf=true;}return isMoreThanHalf;}
/*
函数的功能:在数组中寻找次数超过一半的数。 
*/ 
int findMoreThanHalfNumInArray(int  *arr,int len){if(arr==NULL||len<=0){isExist=false;return -1;}int result;//用来保存结果 int times=0;//用来保存次数 for(int i=0;i<len;i++){if(times==0){//如果times为零时,则应该保存当前的数,并将times=1result=arr[i];times=1; }else if(result==arr[i]){times++;}else{times--;}}//到这里为止,我们还需要判断reuslt是否真的是数组中次数出现一半的数字。if(!isMoreThanHalfNumInArray(arr,len,result)) {isExist=false;return -1;}return result;} 
int main(void){int n;while(scanf("%d",&n)!=EOF){if(n>0){int *arr=(int *)malloc(n*sizeof(int));if(arr==NULL){exit(EXIT_FAILURE);}int val;for(int i=0;i<n;i++){scanf("%d",&val);arr[i]=val;}//开始寻找超过一半的数在数组中isExist=true;int result=findMoreThanHalfNumInArray(arr,n);if(isExist){printf("%d",result);} else{printf("-1");}printf("\n");}}return 0;
} 

这篇关于《剑指Offer》面试题:超过数组长度的一半的数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/987830

相关文章

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

MySQL 获取字符串长度及注意事项

《MySQL获取字符串长度及注意事项》本文通过实例代码给大家介绍MySQL获取字符串长度及注意事项,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 获取字符串长度详解 核心长度函数对比⚠️ 六大关键注意事项1. 字符编码决定字节长度2

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

JAVA数组中五种常见排序方法整理汇总

《JAVA数组中五种常见排序方法整理汇总》本文给大家分享五种常用的Java数组排序方法整理,每种方法结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录前言:法一:Arrays.sort()法二:冒泡排序法三:选择排序法四:反转排序法五:直接插入排序前言:几种常用的Java数组排序

Java实现按字节长度截取字符串

《Java实现按字节长度截取字符串》在Java中,由于字符串可能包含多字节字符,直接按字节长度截取可能会导致乱码或截取不准确的问题,下面我们就来看看几种按字节长度截取字符串的方法吧... 目录方法一:使用String的getBytes方法方法二:指定字符编码处理方法三:更精确的字符编码处理使用示例注意事项方

Java数组初始化的五种方式

《Java数组初始化的五种方式》数组是Java中最基础且常用的数据结构之一,其初始化方式多样且各具特点,本文详细讲解Java数组初始化的五种方式,分析其适用场景、优劣势对比及注意事项,帮助避免常见陷阱... 目录1. 静态初始化:简洁但固定代码示例核心特点适用场景注意事项2. 动态初始化:灵活但需手动管理代

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a