统计数组中各元素出现次数

1.问题描述

        给定一大小为N的整数数组,其元素取值范围为[1,N],请统计各元素出现的次数,并要求时间复杂度为O(n),空间复杂度为O(1)。


2.思路

       若没有空间的限制,则可直接开辟一个大小等于元素最大值的数组对各元素进行统计,并且顺带还进行了时间复杂度为O(n)的排序(比快速排序还快哦)。

       统计n个元素出现的次数,每个元素对应一个次数信息需要存储,那么就需要n个空间;而现在不让开辟新的n个空间,这些信息又必须要记录,那你说该放在哪?当然是原数组中啰!

       不过问题又来了,原数组中的值岂不是会被覆盖掉?好吧,可以先把数组中原来的值拿出来后放跟次数相关的信息就没问题了。对于拿出来的这个值,必须即拿即用(不然堆积起来又要n个空间来存储),由于元素取值范围为[1,N],因此可以将这个值当作数组的索引来访问下个元素(记得-1)。

       还有个问题,若像数字5出现了2次,第一次遇到数字5时,将a[4]中原来的值取出用来访问下一个元素并将a[4]清零后+1表示5已经出现过1次了;第二次遇到数字5时问题就来了,不能将现在a[4]中的值取出用来访问下个元素,只能将a[4]加1表示又遇到5了。也就是说一个元素没被访问过和被访问过两种状态得区别对待,那么,怎么知道元素有没有被访问过呢?之前每个元素需要记录次数相关信息,现在每个元素又要体现有没有被碰过,如何让一个值体现这两种属性呢?我们知道一个数除了大小外还有正负这个属性,因此这里便将被访问过的元素设为负值(因为原数组中元素均为正值且没被碰过),于是第一次遇到某个元素时现将它清零后-1,以后每遇到一次就-1。最后将值取反便得到各元素对应的出现次数。

       最后还有一个问题,就是当再次访问某个元素时,由于其值为负表示其出现的次数,就没办法从中得到访问下一元素所需的信息,程序没办法进行下去!因此,这个时候需要手动指定下一个访问的元素是谁,当然是从头到尾全部指定一遍(尽管被指定的元素可能之前已经被访问过了,没关系,再指定下一个就好了),既保证所用元素都被至少访问过一次又可以使整个过程终止。例如,第一次指定a[0],第二次指定a[1],...,直到a[n-1]为止。还请注意的是,通过手动指定而访问到的元素只需要将值清零而不用-1,因为没有数字指向它,当然出现的次数也就是0啰!

题外话:一个问题之所以不像它看上去那么简单,除了需要奇葩的想法外,就是要处理各种特殊的状况。


3.代码   

#include <stdio.h>
#define N 7

void elementCounter(int array[],int n);

int main(void)
{//测试用例
  int array[N]={5,5,2,4,1,7,5};

  elementCounter(array,N);
  
  return 0;
}

void elementCounter(int array[],int n)
{
  int index;//访问下一个元素用的索引
  int flag;//标志此次访问是否为手动指定的
  int temp;
  int i;
//统计元素出现次数
  for(i=0,index=0,flag=1;i<n; )//因为手动指定从array[0]开始,flag设为1
  {
	if(array[index]>0)//某元素第一次被访问
	{
	  temp=array[index];
	  array[index]=flag-1;//若是被手动指定访问到的,则flag=1,其值为0不算其出现过
	  index=temp-1;//自动指定下一个访问元素
	  flag=0;//因为是第一次被访问,其值可以用来访问下个元素,不需要手动指定
	}
	else//某元素不是第一次被访问
	{
	  array[index]=flag-1+array[index];若是被手动指定访问到的,则flag=1,其出现次数不变
	  index=++i;//手动指定下个访问元素
	  flag=1;//标志下一次访问为手动指定的
	}
  }
//打印元素出现次数
  for(i=0;i<N;i++)
  {
	if(array[i]!=0)//不打印没出现过的
	printf("数字%d出现%d次\n",i+1,-array[i]);
  }
}






  • 12
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值