1. 程式人生 > >51-【巧解】統計無序陣列各元素出現的次數--時間複雜度O(n),空間複雜度O(1)

51-【巧解】統計無序陣列各元素出現的次數--時間複雜度O(n),空間複雜度O(1)

一、問題描述

【題型一】

一個長度大小為n的陣列,陣列中的每個元素的取值範圍在[1,n],且為正整數。
問:如何在時間複雜度為O(n),空間複雜度為O(1)的條件下,統計陣列中不同元素出現的次數。

【題型二】

在一個長度為n的數組裡的所有數字都在0-n-1的範圍內。陣列中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複了幾次。請找出陣列中任意一個重複的數字。
【例如】
如果輸入長度為7的陣列(2,3,1,0,2,5,3),那麼對應的輸出是重複的數字2或者3

二、解題思路

【題型一】

    陣列按序掃描,通過當前元素的值作為下標,找到下一個元素。最後得到的陣列中,下標(因為下標從0開始的,故輸出時需要+1)為陣列中出現的元素,每個下標對應的值取反輸出即是該元素出現的頻率。
    若當前元素小於0,
        則跳過;
    若當前元素大於0,
        則判斷其作為下標索引到的元素是否大於0,
            若大於0,則索引到的元素賦值給當前元素,索引到的元素置為-1;
            若小於0,則索引到的元素自減1,當前元素置為0;

    用正數/負數來區分a[i]上是原來的值,還是用於計數的值


    隨便舉個例子,比如 { 2, 5, 5, 2, 3 }
    看到第一個是 2,即2有1個,所以先將這個“1”儲存到a[2-1]的位置上。但a[2-1]有有效數呀,咋辦?移動一下就行。假設我不用負數,而是用中括號來表示計數,步驟依次是
    { 2, 5, 5, 2, 3 }
    5, [1], 5, 2, 3
    3, [1], 5, 2, [1]
    5, [1], [1], 2, [1]
    [0], [1], [1], 2, [2]
    [0], [2], [1], [0], [2]

    結果是 1有0個, 2有2個, 3有1個, 4有0個, 5有2個

    此類以值做下標

的方法適用條件:

  •     正整數
  •     取值範圍[1,n]
  •     n個數

【題型二】

思路一:排序 -- 時間複雜度O(nlogn)
思路二:雜湊表 -- 時間複雜度O(1) 空間複雜度O(n)
思路三:值為下標 -- 時間複雜度O(n) 空間複雜度O(1)

思路三:以2,3,1,0,2,5,3為例
1)陣列第0號位置為2,2!=0,將0號位置的2與2號位置的1交換,變成 1,3,2,0,2,5,3
2)陣列第0號位置為1,1!=0,將0號位置的1與1號位置的3交換,變成 3,1,2,0,2,5,3
3)陣列第0號位置為3,3!=0,將0號位置的3與3號位置的0交換,變成 0,1,2,3,2,5,3
4)陣列第0號位置為0,0==0,遊標右移至第1號位置,1==1,繼續右移,直到第4號位置
5)陣列第4號位置為2, 2!=4, 2==array[2],說明2重複了,輸出2,演算法結束

思路三前提條件:
1)陣列中數字範圍為[0,n-1]
2)陣列長度為n

三、演算法程式碼

【題型一】n個正整數 -- [1,n] 找重複

#include <stdio.h>
#include <stdlib.h>
/*******************************
Author:tmw
date:2018-3-17
********************************/
int* mark_times_appear(int* array, int len)
{
    if(array==NULL || len<0) return NULL;
    int index = 0;
    int i = 0;
    while(i<len)
    {
        /**當前位為正數有效位,則執行交換**/
        while( array[i] > 0 )
        {
            index = array[i];

            /**待交換的位置上的元素是有效值的情況:
            交換,並將當前待交換的元素下的值改成負數計數值
            **/
            if(array[index-1]>0) /**陣列下標從0開始**/
            {
                array[i] = array[index-1];
                array[index-1] = -1;
            }
            /**待交換的位置上的元素是負數的情況:
            說明該元素出現過一次了,負數計數需要減減來更新記錄
            **/
            else/**若需要返回出現兩次的元素,可以直接在這裡做返回**/
            {
                //return array[i];
                array[i] = 0;
                array[index-1]--;
            }
        }
        /**當前位為負數,緊接著判斷下一位**/
        i++;
    }
    return array;
}

【題型二】n個數[0,n] 找重複

/**************************************************
author:tmw
date:2018-8-22
**************************************************/
#include <stdio.h>
#include <stdlib.h>

#define swap(a,b,t) (t=a,a=b,b=t)
/** findDuplicatedNumber 找到任意一個重複的元素並輸出
* @param int* array -- 存數字的陣列
* @param int array_len -- 陣列長度
**/
int findDuplicatedNumber( int* array, int array_len )
{
    /**入參判斷**/
    if( array == NULL || array_len <= 0 )
        return -1;
    int i;
    /**這一步可省略:驗證陣列內的元素滿足給定條件:元素取值[0,n-1],長度為n**/
    for( i=0; i<array_len; i++ )
    {
        if( array[i] < 0 || array[i] > array_len-1 )
            return -1;
    }

    /**主演算法**/
    int temp=0;
    for( i=0; i<array_len; i++ )
    {
        while( array[i] != i )
        {
            if( array[i] != array[array[i]] )
                swap(array[i], array[array[i]], temp);
            else
                return array[i];
        }
    }
    return -1; //沒找著,返回-1
}

四、測試程式碼及結果

【題型一】的測試結果

int main()
{
    printf("測試程式碼!\n");

    int* array;
    int i;
    array = (int*)malloc(5*sizeof(int));
    printf("請輸入5個數組元素:\n");
    for( i=0;i<5;i++ )
        scanf("%d",&array[i]);
    array = mark_times_apear(array,5);

    for( i=0;i<5;i++ )
        printf("%d ",array[i]);

    return 0;
}

夢想還是要有的,萬一實現了呢~~~ヾ(◍°∇°◍)ノ゙~~~