1. 程式人生 > 其它 >【雜湊表】力扣128:最長連續序列

【雜湊表】力扣128:最長連續序列

給定一個未排序的整數陣列 nums ,找出數字連續的最長序列(不要求序列元素在原陣列中連續)的長度。
請你設計並實現時間複雜度為 O(n) 的演算法解決此問題。
示例:

輸入:nums = [100,4,200,1,3,2]
輸出:4
解釋:最長數字連續序列是 [1, 2, 3, 4]。它的長度為 4。

首先想到一個方法:sorted()排序之後再判斷是否是連續升序序列。但是這種排序的時間複雜度是O(\(nlog_2n\))。

  1. 雜湊表
    考慮列舉陣列中的每個數 x,考慮以其為起點,不斷嘗試匹配 x+1, x+2, ··· 是否存在,假設最長匹配到了 x+y,那麼以 x 為起點的最長連續序列即為 x, x+1, x+2, ···, x+y,其長度為 y+1,不斷列舉並更新答案即可。

對於匹配的過程,暴力的方法是每次 O(n) 遍歷陣列去看是否存在這個數,但更高效的方法是用一個雜湊表儲存陣列中的數,這樣檢視一個數是否存在即能優化至 O(1) 的時間複雜度。

即使是這樣,時間複雜度最壞情況下還是會達到 O(n^2)(外層需要列舉 O(n) 個數,內層需要暴力匹配 O(n) 次),無法滿足題目的要求。但仔細分析這個過程,我們會發現其中執行了很多不必要的列舉,如果已知有一個 x, x+1, x+2, ···, x+y 的連續序列,而我們卻重新從 x+1, x+2 或者是 x+y 處開始嘗試匹配,那麼得到的結果肯定不會優於列舉 x 為起點的答案,因此我們在外層迴圈的時候碰到這種情況跳過即可。

  • 怎麼判斷是否跳過?由於要列舉的數 x 一定是在陣列中不存在前驅數 x−1 的,不然按照上面的分析會從 x−1 開始嘗試匹配,因此每次在雜湊表中檢查是否存在 x-1 即能判斷是否需要跳過了。

另外要注意的是,給定陣列中可能存在相同的元素,因此要先用set()函式去重。
演算法步驟:
步驟:

  1. set(nums) 去重
  2. 遍歷 set(sum),找出"符合起點資質的數字 i":
    • i - 1不在set(nums)中
    • i + 1在set(nums)中
  3. 遍歷這些點,計算以它們為起點的最長連續序列長度

時間複雜度O(n)證明:
在第3步中,遍歷的序列之間沒有重合部分,所以遍歷次數小於等於len(nums),最壞時間複雜度為O(2n)。


時間複雜度:O(n),其中 n 為陣列的長度。具體分析已在上面正文中給出。
空間複雜度:O(n)。雜湊表儲存陣列中所有的數需要 O(n) 的空間。

可以把所有數字放到一個雜湊表,然後不斷地從雜湊表中任意取一個值,並刪掉其之前之後的所有連續數字,然後更新目前的最長連續序列長度。重複這一過程,我們就可以找到所有的連續數字序列。

  1. 動態規劃
    首先,定義一個雜湊表(用於判斷某個數是否存在雜湊表中)
    然後遍歷陣列
    如果當前數num存在雜湊表,那麼跳過
    如果當前數num不存在雜湊表中,那麼查詢num-1和num+1這兩個數是不是在雜湊表中

比如 num==5
1234 678都在雜湊表中
遍歷陣列的時候,遇到1234678都會掠過
此時雜湊表中,1的位置和4存的值都是4(證明1或者4所在的連續長度是4)
同理 6和8存的值都是3
那麼此時5進來之後,看看4和6在不在雜湊表內,如果在的話,一定是端點,一定能獲取到值
所以5進來之後,發現左邊有4個連續的,右邊有3個連續的,加上自己一個,那麼組成一個大連續的
4+1+3 = 8
所以要更新當前最長連續串的端點,也就是1的位置(5-4),8的位置(5+3),更新長度為8
只需要端點存值就行,因為端點中的值在遍歷的時候如果在雜湊表中就會略過
如果這個時候9又進來,那麼它獲取到8的位置有8,10的位置有0
更新連續子串長度(8+1+0)
所以更新左端點1(9-8)的值為9,右端點9(9+0)的值為9

class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        res = 0
        hash_dict = dict()
        for num in nums:
            # 新進來雜湊表一個數
            if num not in hash_dict:
                # 獲取當前數的最左邊連續長度,沒有的話就更新為0
                left = hash_dict.get(num-1,0)
                # 同理獲取右邊的數
                right = hash_dict.get(num+1,0)
                """不用擔心左邊和右邊沒有的情況
                因為沒有的話就是left或者right==0
                並不改變什麼
                """
                # 把當前數加入雜湊表,代表當前數字出現過,存什麼值都可以,比如lenth,只要當前數字能在雜湊表裡佔了個坑就行,重要的是更新端點的值
                hash_dict[num] = 1
                # 更新長度
                length = left+1+right
                res = max(res,length)
                # 更新最左端點的值,如果left==n存在,那麼證明當前數的前n個都存在雜湊表中
                hash_dict[num-left] = length
                # 更新最右端點的值,如果right==n存在,那麼證明當前數的後n個都存在雜湊表中
                hash_dict[num+right] = length
                # 此時 [num-left,num-right] 範圍的值都連續存在雜湊表中了
                # 即使left或者right==0都不影響結果
        return res
  1. 並查集
    https://leetcode.cn/problems/longest-consecutive-sequence/solution/tu-jie-yu-dao-jiu-shen-jiu-bing-cha-ji-by-chun-men/