統計陣列中各元素出現次數

康斯坦丁丶玩世發表於2017-03-20

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]);
  }
}






相關文章