簡體   English   中英

查找時間為O(n),空間為O(1)的重復有符號整數

[英]Finding repeating signed integers with O(n) in time and O(1) in space

(這是對以下內容的概括: 在O(n)時間和O(1)空間中查找重復項

問題:編寫一個C ++或C函數,它們的時間和空間復雜度分別為O(n)和O(1),可以在給定數組中找到重復的整數而不更改它。

示例:給定{1、0,-2、4、4、1、3、1,-2,}函數必須一次打印1,-2和4(以任何順序)。


編輯:以下解決方案要求在數組的最小值到最大值范圍內的每個整數都具有一個二位位(代表0、1和2)。 必需的字節數(與數組大小無關)永遠不會超過(INT_MAX – INT_MIN)/4 + 1

 #include <stdio.h> void set_min_max(int a[], long long unsigned size,\\ int* min_addr, int* max_addr) { long long unsigned i; if(!size) return; *min_addr = *max_addr = a[0]; for(i = 1; i < size; ++i) { if(a[i] < *min_addr) *min_addr = a[i]; if(a[i] > *max_addr) *max_addr = a[i]; } } void print_repeats(int a[], long long unsigned size) { long long unsigned i; int min, max = min; long long diff, q, r; char* duos; set_min_max(a, size, &min, &max); diff = (long long)max - (long long)min; duos = calloc(diff / 4 + 1, 1); for(i = 0; i < size; ++i) { diff = (long long)a[i] - (long long)min; /* index of duo-bit corresponding to a[i] in sequence of duo-bits */ q = diff / 4; /* index of byte containing duo-bit in "duos" */ r = diff % 4; /* offset of duo-bit */ switch( (duos[q] >> (6 - 2*r )) & 3 ) { case 0: duos[q] += (1 << (6 - 2*r)); break; case 1: duos[q] += (1 << (6 - 2*r)); printf("%d ", a[i]); } } putchar('\\n'); free(duos); } void main() { int a[] = {1, 0, -2, 4, 4, 1, 3, 1, -2}; print_repeats(a, sizeof(a)/sizeof(int)); } 

big-O表示法的定義是,其自變量是一個函數( f(x) ),由於該函數( x )中的變量趨於無窮大,因此存在一個常數K ,因此目標成本函數將小於Kf(x) 通常,將f選擇為最小的此類簡單函數,以便滿足條件。 (很明顯如何將以上內容提升為多個變量。)

這很重要,因為不必指定K即可隱藏整個復雜的行為。 例如,如果算法的核心是O(n 2 ),則它允許其他各種O(1),O(logn),O(n),O(nlogn),O(n 3/2 ),等支持隱藏的位, 即使對於實際的輸入數據,那些部分實際上是占主導地位的。 沒錯,這可能完全是誤導! (一些更出色的bignum算法具有真實的屬性。與數學一起說謊是一件很棒的事情。)

那么這要去哪里呢? 好吧,您可以假定int很容易固定大小(例如32位),並使用該信息來避免很多麻煩,並分配固定大小的標志位數組來保存您真正需要的所有信息。 實際上,通過每個潛在值使用兩位(一位代表您是否已經看過該值,另一位代表您是否已打印出該值),則可以使用大小為1GB的固定內存塊來處理代碼。 那么這會給你足夠的標志信息,以應付多達32位整數,你可能會給手柄。 (這在64位計算機上甚至是實用的。)是的,設置該內存塊將花費一些時間,但是它是恆定的,因此它的形式為O(1),因此退出了分析。 鑒於此,您將擁有不變的(但驚人的)內存消耗和線性時間(您必須查看每個值以查看它是否是新值,見過一次等等),而這正是所要的。

不過這是一個骯臟的把戲。 您也可以嘗試掃描輸入列表以計算出范圍,從而在正常情況下可以使用較少的內存。 同樣,這只會增加線性時間,您可以嚴格限制上述所需的內存,因此它是恆定的。 更加棘手,但正式合法。


[編輯]示例C代碼(這不是C ++,但我不太擅長C ++;主要區別在於標志數組的分配和管理方式):

#include <stdio.h>
#include <stdlib.h>

// Bit fiddling magic
int is(int *ary, unsigned int value) {
    return ary[value>>5] & (1<<(value&31));
}
void set(int *ary, unsigned int value) {
    ary[value>>5] |= 1<<(value&31);
}

// Main loop
void print_repeats(int a[], unsigned size) {
    int *seen, *done;
    unsigned i;

    seen = calloc(134217728, sizeof(int));
    done = calloc(134217728, sizeof(int));

    for (i=0; i<size; i++) {
        if (is(done, (unsigned) a[i]))
            continue;
        if (is(seen, (unsigned) a[i])) {
            set(done, (unsigned) a[i]);
            printf("%d ", a[i]);
        } else
            set(seen, (unsigned) a[i]);
    }

    printf("\n");
    free(done);
    free(seen);
}

void main() {
    int a[] = {1,0,-2,4,4,1,3,1,-2};
    print_repeats(a,sizeof(a)/sizeof(int));
}

由於您有一個整數數組,因此可以使用簡單的解決方案對數組進行排序(您沒有說不能修改)並打印重復項。 可以使用Radix sort以O(n)和O(1)的時間和空間復雜度整數數組進行排序 盡管通常可能需要O(n)空間,但是可以使用O(1)空間輕松實現就地二進制MSD基數排序(請在此處查看更多詳細信息)。

O(1)空間約束是棘手的。

根據定義,打印數組本身的事實需要O(N)存儲。

現在,您會感到很慷慨,我將為您提供可以在程序內為緩沖區提供O(1)的存儲,並考慮到程序外部占用的空間對您而言無關緊要,因此輸出不是問題...

盡管如此,由於輸入數組上的不變性約束,O(1)空間約束仍然讓人感到棘手。 可能不是,但感覺是這樣。

由於您試圖將O(N)信息存儲在有限的數據類型中,因此您的解決方案會溢出。

這里的定義存在棘手的問題。 O(n)是什么意思?

康斯坦丁的答案聲稱,基數排序時間復雜度為O(n)。 實際上,它是O(n log M),其中對數的底數是所選的基數,M是數組元素可以具有的值的范圍。 因此,例如,一個二進制基數的32位整數將具有log M = 32。

因此,從某種意義上來說,這仍然是O(n),因為log M是一個獨立於n的常數。 但是,如果我們允許這樣做,那么有一個更簡單的解決方案:對於范圍內的每個整數(它們中的所有4294967296),遍歷數組以查看它是否出現多次。 從某種意義上講,這也是O(n),因為4294967296也是一個獨立於n的常數。

我不認為我的簡單解決方案可以作為答案。 但是,如果沒有,那么我們也不應該允許基數排序。

我真的看不到如何只有O(1)空間而不修改初始數組。 我的猜測是您需要其他數據結構。 例如,整數的范圍是多少? 如果它是0..N(就像您鏈接的另一個問題一樣),則可以有一個大小為N的附加計數數組。然后在O(N)中遍歷原始數組,並在當前元素的位置增加計數器。 然后遍歷另一個數組並打印count> = 2的數字。類似:

int* counts = new int[N];
for(int i = 0; i < N; i++) {
    counts[input[i]]++;
}

for(int i = 0; i < N; i++) {
    if(counts[i] >= 2) cout << i << " ";
}

delete [] counts;

我懷疑這是可能的。 假設有一個解決方案,讓我們看看它是如何工作的。 我將盡我所能,並證明它不起作用...那么,它如何起作用?

不失一般性,我們可以說我們處理了k次數組,其中k是固定的。 當有m個重復項且m >> k時,該解決方案也應適用。 因此,在至少一次通過中,我們應該能夠輸出x個重復項,其中x隨着m的增長而增長。 為此,一些有用的信息已在上一遍中計算出並存儲在O(1)存儲中。 (數組本身無法使用,這將提供O(n)存儲。)

問題是:我們有O(1)個信息,當我們遍歷數組時,我們必須標識x個數字(以輸出它們)。 我們需要一個O(1)存儲,如果其中有一個元素,則需要在O(1)時間里告訴我們。 或用不同的方式表示,我們需要一個數據結構來存儲n個布爾值(其中x為true ),這些布爾值使用O(1)空間,並花費O(1)時間進行查詢。

該數據結構是否存在? 如果沒有,那么我們將無法在具有O(n)時間和O(1)空間的數組中找到所有重復項(或者有些花哨的算法以完全不同的方式工作?)。

假設您可以使用以下事實:您沒有使用所有可用空間。 每個可能的值只需要再增加一位,並且32位int值中有許多未使用的位。

這有嚴重的局限性,但在這種情況下有效。 數字必須在-n / 2和n / 2之間,並且如果它們重復m次,它們將被打印m / 2次。

void print_repeats(long a[], unsigned size) {
    long i, val, pos, topbit = 1 << 31, mask = ~topbit;
    for (i = 0; i < size; i++)
        a[i] &= mask;

    for (i = 0; i < size; i++) {
        val = a[i] & mask;
        if (val <= mask/2) {
           pos = val;
        } else {
            val += topbit;
            pos = size + val;
        }
        if (a[pos] < 0) {
            printf("%d\n", val);
            a[pos] &= mask;
        } else {
            a[pos] |= topbit;
        }
    }
}

void main() {
    long a[] = {1, 0, -2, 4, 4, 1, 3, 1, -2};
    print_repeats(a, sizeof (a) / sizeof (long));
}

版畫

4
1
-2

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM