該問題是這樣的:
假如給你20億個非負數的int型整數,然后再給你一個非負數的int型整數 t ,讓你判斷t是否存在於這20億數中,你會怎么做呢?
有人可能會用一個int數組,然后把20億個數給存進去,然后再循環遍歷一下就可以了。
想一下,這樣的話,時間復雜度是O(n),所需要的內存空間
4byte * 20億,一共需要80億個字節,
大概需要8GB的內存空間,顯然有些計算機的內存一次是加載不了這么這么多的數據的。
初步優化
按照上面的做法,時間復雜度是O(n),內存是8GB,實際上我們是可以把時間復雜度降低到O(1)的。
例如我們可以這樣來存數據,把一個int非負整數n作為數組下標,如果n存在,則對應的值為1,如果不存在,對應的值為0。例如數組arr[n] = 1,表示n存在,arr[n] = 0表示n不存在。
那么,我們就可以把20億個數作為下標來存,之后直接判斷arr[t]的值,如果arr[t] = 1,則代表存在,如果arr[t] = 0,則代表不存在。這樣,我們就可以把時間復雜度降低到O(1)。不過空間復雜度我們並沒有降低。還稍微大了點。
由於int非負整數一共有 2^31 個,所以數組的大小需要 2^32 這么大。
這里可能有人說也可以用HashSet來存啊,時間復雜度也是近似O(1)。不過這里需要說明的是,HashSet里面存的必須是對象,也就是說需要把int包裝成Integer,顯然一個對象的話是更花銷內存的,需要對象頭啊什么的…..
再次優化
大家想一個問題,對於一個數,實際上我們只需要兩種狀態,就是這個數存在和不存在這兩種可能。上面我們用1代表存在,用0代表不存在。
也就是說,我們是可以不用int型的數組來存儲的,一個int型占用4個字節,即32個二進制位,一共可以表示40億多個狀態。用int型的來存兩個狀態,多浪費。
所以我們可以考慮用boolean型的來存的,boolean貌似就占用一個字節(java中的boolena貌似是占用一個字節)。而一個boolean有true和false兩種狀態,所以也是成立的。這樣子的話占用的內存就是2GB的內存了。
這樣,就可以降低到之前的四分之1內存了。
最終優化:bitmap
大家再想一個問題,雖然boolean是表示兩種狀態,但是boolean實際上占用了8bit啊,按道理8bit是可以表示128種狀態的。而被我們拿來表示兩個狀態,是否也有點浪費了呢?
我們都知道,一個二進制位,有0和1兩種狀態,所以說,其實我們是可以用一個二進制位來代表一個int型的數是否存在的。例如對於1,3,5,7這四個數,如果存在的話,則可以這樣表示:
1代表這個數存在,0代表不存在。例如表中01010101代表1,3,5,7存在,0,2,4,6不存在。
那如果8,10,14也存在怎么存呢?如圖,8,10,14我們可以存在第二個字節里
以此類推。這樣子,我們又可以把內存降低到之前的8分之一了。
這種采用一個二進制位來存儲數據的方法,我們也叫做bitmap算法。
可能有人會問,假如我要添加一個數n,我知道它要存在第n個位那里,把第n個二進制改為1,可是我要怎么操作呢?
這個對於bitmap算法是如何存儲的,如何進行增刪操作的,我會在之后的文章里講。
Java中有自帶的bitmap實現,今天我們就用Java中自帶的bitmap來做道題練練手。我們換道類似題目吧,不知道你一眼是否就能想到用bitmap算法來做。
題目描述:
現在有五十億個int類型的正整數,要從中找出重復的數並返回。
判斷50億個數有哪些是重復和剛才上面那個判斷是否存在,其實是一樣的。我們采用bitmap算法來做。不過這里50億個數,別人肯定是以文件流的形式給你的。這樣我們為了方便,我們就假設這些數是以存在int型數組的形式給我們的。
代碼如下:
public class Test {
//為了方便,假設數據是以數組的形式給我們的
public static Set<Integer> test(int[] arr) {
int j = 0;
//用來把重復的數返回,存在Set里,這樣避免返回重復的數。
Set<Integer> output = new HashSet<>();
BitSet bitSet = new BitSet(Integer.MAX_VALUE);
int i = 0;
while (i < arr.length) {
int value = arr[i];
//判斷該數是否存在bitSet里
if (bitSet.get(value)) {
output.add(value);
} else {
bitSet.set(value, true);
}
i++;
}
return output;
}
//測試
public static void main(String[] args) {
int[] t = {1,2,3,4,5,6,7,8,3,4};
Set<Integer> t2 = test(t);
System.out.println(t2);
}
}
打印結果:
[3, 4]
作者簡介
一名熱愛編程、寫作的Java工程師,喜結天下英雄豪傑,歡迎關注我的公眾號一起學習交流

更多文章,可以前往文章目錄導航:文章目錄導航
