[英]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(以任何順序)。
(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.