【剑指offer】出现次数超过一半的数字「建议收藏」

【剑指offer】出现次数超过一半的数字

大家好,又见面了,我是全栈君。

转载请注明出处:http://blog.csdn.net/ns_code/article/details/26957383

题目描写叙述:

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

比如输入一个长度为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

    思路:

    1、一个数字在数组中出现次数超过了一半,则排序后,位于数组中间的数字一定就是该出现次数超过了长度一半的数字(能够用反证法证明)。也即是说,这个数字就是统计学上的中位数。最easy想到的办法是用高速排序对数组排序号后,直接取出中间的那个数字,这样的时间复杂度为O(nlogn)。空间复杂度为O(1)。

    2、其实能够不用对数组进行排序。或者说仅部分排序,受高速排序的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)。

    我用该方法写了下代码,并在九度OJ上run。报了超时。代码例如以下:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<stdbool.h>
bool IsExisted;

void Swap(int *a,int *b)
{
	if(*a != *b)
	{
		*a = *a + *b;
		*b = *a - *b;
		*a = *a - *b;
	}

}

/*
算法导论版快排的Partition函数
*/
int Partition(int *A,int low,int high)
{
	if(A==NULL || low<0 || high<0 || low>=high)
		return -1;
	
	int small = low-1;
	int j;
	for(j=low;j<high;j++)
	{
		if(A[j] <= A[high])
		{
			++small;
			if(j != small)
				Swap(&A[j],&A[small]);
		}
	}
	++small;
	Swap(&A[small],&A[high]);
	return small;
}

int Random_Partition(int *A,int low,int high)
{
	//设置随机种子
	srand((unsigned)time(0));
	int index = low + rand()%(high-low+1);
	Swap(&A[index],&A[high]);
	return Partition(A,low,high);
}


/*
推断keywordkey在数组A中出现的次数是否超过一半
*/
bool IsMoreThanHalf(int *A,int len,int key)
{
	int times = 0;
	int i;
	for(i=0;i<len;i++)
		if(A[i] == key)
			times++;
	if(2*times <= len)
		return false;
	else
		return true;
}
 
/*
返回数组A中出现次数超过一半的数字
基于Partition函数的实现
*/
int MoreThanHalf(int *A,int len)
{
	if(A==NULL || len<1)
	{
		IsExisted = false;
		return -1;
	}

	int low = 0;
	int high = len-1;
	int mid = len>>1;
	int index = Random_Partition(A,low,high);
	while(index != mid)
	{
		if(index > mid)
			index = Random_Partition(A,low,index-1);
		else
			index = Random_Partition(A,index+1,high);
	}

	int key = A[mid];
	if(!IsMoreThanHalf(A,len,key))
	{
		IsExisted = false;
		return -1;
	}
	return key;
}

int main()
{
	int n;
	while(scanf("%d",&n) != EOF)
	{
		int *A = (int *)malloc(sizeof(int)*n);
		if(A == NULL)
			exit(EXIT_FAILURE);

		int i;
		for(i=0;i<n;i++)
			scanf("%d",A+i);

		IsExisted = true;
		int key = MoreThanHalf(A,n);
		if(IsExisted)
			printf("%d\n",key);
		else
			printf("-1\n");
	}
	return 0;
}

    显然,这并不算太好的方法,并且每次随机选取枢轴元素后,都要进行交换操作。且在每次的移动过程中,也要进行交换操作,比較耗时。

    算法导论第九章上给出了一种基于Partition的在最坏情况下也能以O(n)执行的选择第k小的数字的方法(选择中位数是选择地k小元素的特殊情况,这里k=n/2)。这样的方法能够保证选择的枢轴元素在中位数的附近,算法导论上并给出了具体的证明。证明该方法在最坏情况下的时间复杂度也为O(n)。

在改动划分算法后。我们通过下面步骤来实如今n个元素的数组中找第i小元素的SELECT:
1、把数组A分成ceiling(n/5)个组。除最后一组外。每组都有5个元素,最后一组有n mod 5个元素。
2、对每组(的五个元素)用插入法进行排序。然后选出该组的中位数,即排好序的第三个元素;
3、对于步骤2中得到的全部的中位数,通过递归调用SELECT来找到它们的(下)中位数x,(也就是找到第2步得到的全部中位数中第floor(ceiling(n/5) / 2)小个元素)。
4、利用改动后的划分算法把元素分成小于x和大于x的两个子数组。假设设k为划分低区的元素个数加一。则x就是A中第k小的元素;
5、假设i = k。那我们就返回x,它便是我们要找的值。假设i < k,我们就在第4步里的划分低区继续递归调用SELECT来找到第i小的元素。假设i > k,我们就在划分高区递归调用SELECT找第i-k小的数。

    须要注意的是,算法中每一个分组大小为5。假设改成3是不行的(每组为3的时间复杂度为O(NlgN)

假设分成组数为奇数的话,每组大小要>=5才干保证O(N)的时间。

    3、考虑用哈希,key保存数组元素,value保存出现的次数。这样在遍历O(n)能做出key-value的映射。再用O(k)(k为须要的槽的个数)能够找出出现次数超过一半的key,可是因为数组中元素的大小范围未知,因此使用这样的方法,首先不能确定哈希表的大小。即使通过遍历一次求得了最大值,范围非常大的话。又要花费非常大心思设计非常好的哈希函数来完毕key-value的映射,且不具有通用性,并且还要考虑数组中元素为负值的情况,因此用哈希表不合适。

    4、网上非常流行的做法。因为该数字的出现次数比全部其它数字出现次数的和还要多,因此能够考虑在遍历数组时保存两个值:一个是数组中的一个数字,一个是次数。。

当遍历到下一个数字时。假设下一个数字与之前保存的数字同样,则次数加1,假设不同,则次数减1。假设次数为0,则须要保存下一个数字,并把次数设定为1。因为我们要找的数字出现的次数比其它全部数字的出现次数之和还要大,则要找的数字肯定是组后一次把次数设为1时相应的数字。该方法的时间复杂度为O(n),空间复杂度为O(1)。

    用该思路写出的代码AC,例如以下:

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
bool IsExisted;

/*
推断keywordkey在数组A中出现的次数是否超过一半
*/
bool IsMoreThanHalf(int *A,int len,int key)
{
	int times = 0;
	int i;
	for(i=0;i<len;i++)
		if(A[i] == key)
			times++;
	if(2*times <= len)
		return false;
	else
		return true;
}

/*
找出出现次数超过一半的数字
*/
int MoreThanHalfDP(int *A,int len)
{
	if(A==NULL || len<1)
	{
		IsExisted = false;
		return -1;
	}
	
	int result = A[0];
	int times = 1;
	int i;
	for(i=1;i<len;++i)
	{
		if(times == 0)
		{
			result = A[i];
			times = 1;
		}
		else if(A[i] == result)
			++times;
		else
			--times;
	}
	if(!IsMoreThanHalf(A,len,result))
	{
		IsExisted = false;
		return -1;
	}
	return result;
}

int main()
{
	int n;
	while(scanf("%d",&n) != EOF)
	{
		int *A = (int *)malloc(sizeof(int)*n);
		if(A == NULL)
			exit(EXIT_FAILURE);

		int i;
		for(i=0;i<n;i++)
			scanf("%d",A+i);

		IsExisted = true;
		int key = MoreThanHalfDP(A,n);
		if(IsExisted)
			printf("%d\n",key);
		else
			printf("-1\n");
	}
	return 0;
}
/**************************************************************
    
Problem: 1370
    
User: mmc_maodun
    
Language: C
    
Result: Accepted
    
Time:50 ms
    
Memory:1304 kb
****************************************************************/

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/116221.html原文链接:https://javaforall.net

(0)
上一篇 2022年1月24日 下午1:00
下一篇 2022年1月24日 下午1:00


相关推荐

  • git 修改远程仓库地址[通俗易懂]

    一、Git修改远程仓库地址方法:1.直接修改config文件2.使用命令修改远程仓库地址gitremote查看所有远程仓库,gitremotexxx查看指定远程仓库地址gitremoteset-urloriginhttp:/xxxx/john/git_test.git3.删除仓库,添加新仓库1.gitremote查看所有远程仓库,gitremotexxx查看指定远程仓库地址2.gitremotermorigin…

    2022年4月15日
    41
  • Odin Inspector 系列教程 — Enum Toggle Buttons Attribute

    Odin Inspector 系列教程 — Enum Toggle Buttons AttributeEnumToggleButtonsAttribute特性:在水平按钮组中绘制枚举而不是下拉列表。枚举多选按钮主要是应用了System.FlagsusingSirenix.OdinInspector;usingUnityEngine;publicclassEnumToggleButtonsAttributeExample:…

    2022年7月21日
    23
  • minicom指令_minicom 串口通信设置

    minicom指令_minicom 串口通信设置L文件捕获开关。打开时,所有到屏幕的输出也将被捕获到文件中。M发送modem初始化串。若你online,且DCD线设为on,则modem被初始化前将要求你进行确认。O配置minicom。转到配置菜单。P通信参数。允许你改变bps速率,奇偶校验和位数。Q不复位modem就退出minicom。如果改变了macros,而且未存盘,会提供你一个save的机会。R接收文件。从各种协议(外部)中进行选择。若f…

    2022年4月29日
    264
  • IDEA搭建Android开发环境[通俗易懂]

    IDEA搭建Android开发环境[通俗易懂]开发环境IDEA2019.3+SDK+JDK1.8。关于JDK的安装参考:JDK安装以及环境变量的配置,这里就不再说了。直接从SDK的安装开始。一、SDK的下载官方下载地址:sdk下载。不过服务器可能进不去。因为不用AndroidStudio,所以拉到最下面,选择sdk-tools就行下载完成后,解压到一个目录下即可。二、IDEA配置SDK打开Configure->Str…

    2022年7月23日
    235
  • css 相对定位 position relative

    css 相对定位 position relativecss 相对定位 nbsp nbsp nbsp nbsp nbsp 这里相对的意思是 相对于一个元素没有定位前显示的位置 也就是原来显示的位置 nbsp nbsp nbsp nbsp 这个需要注意 nbsp nbsp nbsp nbsp 下面分两个部分来看相对定位 第一部分 如何实现相对定位 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 代码 1 没有加定位的情况下 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp htmlheadm

    2026年3月17日
    1
  • 「源力觉醒 创作者计划」_基于 PaddlePaddle 部署 ERNIE-4.5-0.3B 轻量级大模型实战指南

    「源力觉醒 创作者计划」_基于 PaddlePaddle 部署 ERNIE-4.5-0.3B 轻量级大模型实战指南

    2026年3月12日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号