問題
給出一個未排序的整數數組,找出最長的連續元素序列的長度。
如:
給出[100, 4, 200, 1, 3, 2],
最長的連續元素序列是[1, 2, 3, 4]。返回它的長度:4。
你的算法必須有O(n)的時間復雜度。
初始思路
要找連續的元素,第一反應一般是先把數組排序。但悲劇的是題目中明確要求了O(n)的時間復雜度,要做一次排序,是不能達到的。不過我們還是先來看看排序的方案要怎么實現。
簡單來說,排序后我們只要遍歷數組,檢查當前值減1是否等於上一個值,如果等,增加當前連續序列的計數;如果不等,將當前計數與當前最大值比較,如果更優替換最大值, 並重置計數為1。具體到細節上,我們還要考慮幾個問題:
- 第一個數
處理第一個數時是沒有上一個值的。有人可能覺得可以給存儲上一個值的變量賦一個特別的初始值來表示處理的是第一個數。但是由於數組內元素的取值范圍是所有的整數,不可能找出一個特別的值。所以代碼中需要對第一個數做特殊的判斷
- 重復的數
數組中可能會有重復的數,所以我們不能光判斷當前值減1等於或不等於上一個值。還需要加上一個等不等與上一個值的判斷,如果等,說明是一個重復的數字,直接不做任何處理跳到數組中的下一個數。
- 最后一組連續序列
由於我們只在遍歷中發現當前值減1不等於上一個值時才嘗試更新序列長度最大值。如果有一個連續序列一直持續到數組中的最后一個元素,這個序列的長度是沒有得到處理的。因此我們需要在遍歷完數組后再嘗試更新依稀最大值。
加入了這些細節考慮后,代碼就呼之欲出了:

1 class SolutionSort 2 { 3 public: 4 int longestConsecutive(std::vector<int> &num) 5 { 6 std::sort(num.begin(), num.end()); 7 8 int maxLen = 0; 9 int currentLen = 0; 10 int previous = 0; 11 12 for(auto iter = num.begin(); iter != num.end(); ++iter) 13 { 14 if(iter == num.begin())//第一個數的特殊情況 15 { 16 ++currentLen; 17 } 18 else 19 { 20 if(*iter == previous)//重復數的情況 21 { 22 continue; 23 } 24 else if(*iter - 1 == previous) 25 { 26 ++currentLen; 27 } 28 else 29 { 30 if(currentLen > maxLen) 31 { 32 maxLen = currentLen; 33 } 34 currentLen = 1; 35 } 36 } 37 previous = *iter; 38 } 39 40 //有一個連續序列持續到數組最后一個元素的情況 41 if(currentLen > maxLen) 42 { 43 maxLen = currentLen; 44 } 45 46 return maxLen; 47 } 48 };
提交后發現其實是可以通過Judge Small和Judge Large的。但是這種方案始終不符合題目要求。
優化
要實現O(n)的時間復雜度,就不能對數組排序。其實我們大腦在判斷這個問題時就是不排序的。讓我們來模擬一下:
你看到[100, 4, 200, 1, 3, 2]這個數組,首先你會看99或者101在不在這個數組里,發現數組沒這兩個數,那么100組成的連續序列長度僅為1。接着會看5或者3在不在數組里,會發現3存在,5不存在;緊接着會看2在不在....直到發現0不在。從而得到4組成的最長序列為4。
總結一下會發現,我們在判斷某個數的連續序列時,會分別往減小和增大的方向找下一個連續數在不在數組中。然后把兩個方向的長度加起來即為包含該數的一個連續序列。需要注意的是,當前數的長度計數只需要出現在一個方向的查找中計算即可,否則就重復了。要找一個數是不是在數組中,不可能用遍歷的方法實現,這樣時間復雜度就超過O(n)了。而要降低時間復雜度,一個經典的方案就是空間換時間。用增加空間復雜度的方法來換取時間復雜度的降低。所以我們可以先對數組進行一次預處理,生成一份包含數組元素的哈希表。這樣在求解某個數字在不在數組時就可以得到O(1)的時間復雜度。
那么我們可以得到如下偽代碼:
找連續序列函數(要找序列的值,方向)
循環直到要找的值不在哈希表中
序列長度+1
如果增加方向,要找的序列值+1
如果減少方向,要找的序列值-1
循環結束
返回序列長度
找連續序列函數結束
求解函數(數組)
遍歷數組生成哈希表
遍歷數組
序列長度1 = 找連續序列函數(當前值,增加方向)
序列長度2 = 找連續序列函數(當前值 - 1,減少方向)
如果序列長度1 + 序列長度2 > 當前最長序列,更新最長序列
遍歷結束
求解函數結束
這個方案的時間復雜度應該是O(n) + O(n) * O(1) * O(平均序列長度)。如果平均序列長度等於n,如數組[3,4,2,1],復雜度就是O(n^2)了。看來還不可行,主要的時間復雜度都浪費在找連續序列上了。怎么能減少找連續序列的時間復雜度?經過觀察我們可以發現,4的最長序列和3,2,1的最長序列其實是一樣的。找過了4之后其實后面這3個數都不用找了。而我們控制是否查找一個數的連續序列是通過判斷數字是否在哈希表中來實現的,也就是說,如果我們可以在找出一個數字在連續序列中后就將其移除,就可以避免以后再觸發查找的循環。通過這個優化,時間復雜度將變為O(n) + O(n) + O(序列長度總和),可以認為是O(n)了。最后得出代碼如下:

1 classSolution 2 { 3 public: 4 int longestConsecutive(std::vector<int> &num) 5 { 6 for(int i = 0; i < num.size(); ++i) 7 { 8 flags_.insert(num[i]); 9 } 10 11 int maxLen = 0; 12 13 for(int i = 0; i < num.size(); ++i) 14 { 15 int ascendingMax = FindConsecutiveNumbers(ASCENDING, num[i]); 16 int decendingMax = FindConsecutiveNumbers(DECENDING, num[i] - 1); 17 18 19 if(ascendingMax + decendingMax > maxLen) 20 { 21 maxLen = ascendingMax + decendingMax; 22 } 23 } 24 25 return maxLen; 26 } 27 28 private: 29 enum OrderBy 30 { 31 ASCENDING, 32 DECENDING 33 }; 34 35 int FindConsecutiveNumbers(OrderBy orderBy, int value) 36 { 37 int maxLen = 0; 38 39 while(flags_.find(value) != flags_.end()) 40 { 41 ++maxLen; 42 43 flags_.erase(value); 44 45 if(orderBy == ASCENDING) 46 { 47 ++value; 48 } 49 else 50 { 51 --value; 52 } 53 } 54 55 return maxLen; 56 } 57 58 std::unordered_set<int> flags_; 59 };