給一個整數數組a[], 找到其中包含最多連續數的子集,比如給:15, 7, 12, 6, 14, 13, 9, 11,則返回: 5:[11, 12, 13, 14, 15] 。
最簡單的方法是sort然後scan一遍,但是要 o(nlgn) , 有什麼 O(n) 的方法嗎?
思路:
網上有人用map來做,個人覺得用map的復雜度還是O(nlgn)。並查集可以做到O(n),但網上一直沒有看到完整的代碼,所以自己寫了一個。
先簡單介紹並查集的內容,算法導論和網上都可以找到相應的資料。
並查集是一宗簡單的用途廣泛的算法和數據結構。並查集是若干個不相交集合,能夠實現較快的合並和判斷元素所在集合的操作。應用很多,比如:求無向圖的連通分量個數,實現kruskal算法等。
並查集可以方便地進行以下三種操作:
1、Make(x):把每一個元素初始化為一個集合,初始化後每一個元素的父節點就是它本身。
2、Find(x):查找一個元素所在的集合,一個元素所在的集合用這個集合的祖先節點來標識。判斷兩個元素是否屬於同一個集合,只要看他們所在集合的祖先節點是否相同即可。
3、Union(x, y):合並x、y所在的兩個集合,先利用Find()找到兩個集合的祖先,若這兩個祖先節點不是同一個節點,將其中一個祖先節點指向另一個祖先節點即可。(具體哪個祖先指向哪個祖先可以根據實際情況而定)
並查集的優化:在Find函數中,每次找祖先節點的復雜度是O(n)。當我們經過遞歸找祖先節點的時候,順便把這條路徑上的所有子孫節點都直接指向祖先,這樣下次Find的時候復雜度就變成了O(1)。
回到題目,首先調用Make(x)將每個元素變成一個並查集,然後一次掃描a[i],查看a[i]-1是否存在,若存在調用Union(a[i], a[i]-1);查看a[i]+1是否存在,若存在調用Union(a[i]+1, a[i])。在合並的同時更新集合的大小。接下裡的問題是怎麼判斷a[i]-1和a[i]+1是否存在,用哈希可以解決,而且復雜度是O(1)。
該題中並查集的操作都是基於下標的。我們用p[i]表示a[i]的父節點的下標,用len[i]表示以a[i]為根的集合的大小,我們合並的時候總是將較小值集合的祖先指向較大值集合的祖先,這樣就只需要記錄較大值集合的祖先節點對應的長度。最後掃描數組a[]和len[],找到最大長度maxLen對應的a[i]。最後的結果就是:a[i]-maxLen+1, a[i]-maxLen+2, ..., a[i]。
#include <iostream> #include <vector> #include <assert.h> using namespace std; void Make(vector<int>& p, vector<int>& len, int x) { p[x] = x; //x是下標 len[x] = 1; //一個節點的集合長度為1 } int Find(vector<int>& p, int x) { if (x != p[x]) p[x] = Find(p, p[x]); //路徑壓縮,將該路徑上所有子孫節點,即集合中所有子孫節點的父節點都為根節點 return p[x]; } void Union(vector<int>& p, vector<int>& len, int x, int y) {//傳參的時候,要將較大值的節點傳給x,較小值的節點傳給y int px = Find(p, x); int py = Find(p, y); if (px == py) return; p[py] = px; //將py指向px len[px] += len[py]; //px為新的祖先,px的值要大於py的值,所以只更新px的長度即可 } void Longest(vector<int>& ivec, int& max, int& maxLen) { assert(!ivec.empty()); int size = ivec.size(); vector<int> p(size); vector<int> len(size); for (int i = 0; i < size; ++i) Make(p, len, i); int MAX = ivec[0]; for (int i = 1; i < size; ++i) { if (ivec[i] > MAX) MAX = ivec[i]; } vector<int> hash((MAX+2), -1); //用於查找a[i]-1和a[i]+1是否存在 for (int i = 0; i < size; ++i) hash[ivec[i]] = i; for (int i = 0; i < size; ++i) { int num = ivec[i]; if (hash[num] == i) //這個判斷條件用於處理重復數字,若hash[num]!=i,說明在i之後還有num重復出現,只處理最後一個即可 { if (hash[num-1] != -1) Union(p, len, i, hash[num-1]); if (hash[num+1] != -1) Union(p, len, hash[num+1], i); } } max = ivec[0]; maxLen = len[0]; for (int i = 1; i < size; ++i) { if (len[i] > maxLen) { maxLen = len[i]; max = ivec[i]; } } } int main() { int a[] = {15, 7, 12, 6, 14, 13, 9, 11}; vector<int> ivec(a, a + 8); int max, maxLen; Longest(ivec, max, maxLen); for (int i = max-maxLen+1; i <= max; ++i) cout << i << ' '; cout << endl; return 0; }