contributed by < Risheng1128
>
1
延伸題目:
GOLDEN_RATIO_PRIME
,探討其實作考量在研究程式碼運作原理之前,這邊先討論題目使用到的資料結構,分為 hlist_head
、 hlist_node
、 hash_key
、 map_t
首先觀察結構 map_t
,由一個整數 bits
以及一個指向結構 hlist_head
的指標 ht
所組成
其中一個成員 bits
是用來決定 hash table 的大小,可以從巨集 MAP_HASH_SIZE()
得知,從原始碼我們可以知道會產生 2bits 個 struct hlist_head
大小的 hash table
而成員 ht
則是指向 hash table 的指標,在 map_init()
中可以看到 ht
指向了 hash table
接著觀察 struct hash_key
,比較特別在於該結構包含著結構 struct hlist_node
,可以看出是用來存放資料且主要架構為 linked list
最後觀察 struct hlist_head
及 struct hlist_node
,其中比較特別的地方在於 struct hlist_node
的 pprev
被定義為指標的指標,而使用該定義的原因可以參考第 1,2 週課堂問答簡記
依據上述的這些結構,可以簡單畫出整個 hash table 的示意圖,如下圖所示
由上圖我們可以得知整個 hash table 的架構,結構 map_t
的成員 ht
指向型態為 hlist_head
的陣列,接著每個 hlist_head
的成員 first
後會接著節點型態為 hlist_node
的 linked list
從函式 twoSum()
開始分析,以下為完整的原始碼
twoSum()
在 twoSum
line 3,首先執行函式 map_init()
,其目的為建立完整的 hash table ,並且初始化,以下說明 map_init()
的實作流程:
map_t
的空間bits
建立 2bits 個 struct hlist_head
大小的空間,這邊使用到前面提過的巨集 MAP_HASH_SIZE()
hlist_head
其成員 first
設定為 NULL
接著在 twoSum()
line 10 的位置,呼叫了函式 map_get()
,這邊把函式 map_get()
及函式 find_key
一起做分析
在 map_get()
的部份比較直覺,主要步驟就是呼叫 find_key()
尋找是否已經有互補 (target - nums[i]
) 的資料,有的話就回傳包含該資料的結構 hash_key
的 data
,反之則回傳 NULL
而 find_key()
的部份較為複雜,以下為分析過程
首先建立一個指向結構 struct hlist_head
指標 head
,將 head
指向 hash table 的某一格欄位,而要決定選哪一格欄位是透過函式 hash()
執行,目前先把函式 hash()
當成一個 hash function 只用,詳細資料會在下一部份做解釋
接著走訪整個 linked list 找到對應的 key
,這邊利用巨集函式 container_of()
找到包含節點的 hash_key
的起始地址,如果找到就直接回傳起始地址,失敗就回傳 NULL
在 twoSum()
的 line 19 呼叫了函式 map_add()
,其目的在於當 hash table 沒有互補的資料時,把目前的資料 nums[i]
加到 hash table 裡,從原始碼得知範例是將變數 i
的值作為 key
使用
在函式 map_add()
裡,首先判斷是否已經有該資料點,若已經存在則直接離開函式
建立新的 hash_key
結構,並把 key
及 data
複製到結構成員裡
接著將節點加進 hash table,實作方法為把節點加在第一個位置
經過上述步驟,可以把節點加進 linked list 的第一個位置
最後在 twoSum()
的 line 23 呼叫 map_deinit()
,釋放整個 hash table ,原始碼如下所示
map_deinit()
2
延伸問題:
參考 82. Remove Duplicates from Sorted List II 想法思考
int
大小的陣列 freq
用來紀錄每個資料出現的次數使用此方法可以讓時間複雜度只有 O(n)
這邊採用自己 開發紀錄 (lab0-c) 的實作方法
3
延伸問題:
在分析整個流程之前,首先先將整個 Cache 及資料的結構釐清,以下為分析結果
從上面的程式碼可以歸類出兩種結構,分別是 Cache 及資料節點,示意圖如以下所示
lRUCachePut()
: 添加資料到 Cache 裡lRUCacheGet()
: 利用 key 回傳 cache 的資料,無資料則回傳 -1lRUCacheFree()
: 釋放所有記憶體空間這邊參考 146. LRU Cache 所給的範例,並撰寫測試程式,如以下所示:
測試結果,和 leetcode 所給的答案一致
To do: 目前想法是原本加資料和取資料的流程都會走訪整個 linked list ,會導致時間複雜度不會保持 O(1),因此會往減少時間複雜度的方向思考
4
延伸問題:
探討本題的作法,可以從 struct seq_node
及函式 longestConsecutive()
前半段開始
從上述的程式碼可以得知,程式主要架構是建立一個 hash table ,並且計算每個資料 nums[i] 的 hash ,將不重複的資料複製到 seq_node 並加到計算出的 hash table 的位置,以下為示意圖:
圖中的 linked list 都為雙向環狀結構
解題思路
大概了解整個程式的運作後,可以知道 left
和 right
的功能是要分別找比較大和比較小的值,至於 LLL
和 RRR
的關鍵在於 int left = num, right = num;
,可以得知 left
和 right
的起始值都是 num
,因此我們可以得知最精簡的方式就是直接分別把 left
減 1
且 right
加 1
,因此我們可以得到以下解答:
LLL
= --left
RRR
= ++right
longestConsecutive()
: 找到最長的連續序列,回傳其長度find()
: 尋找資料 num 有沒有存在資料節點 seq_node 上這邊參考 128. Longest Consecutive Sequence 所給的範例,並撰寫測試程式,如以下所示:
測試結果如下,和 leetcode 的答案符合
首先,在函式 longestConsecutive()
第二次走訪整個資料的時候,會進到 while (node)
,從整個程式邏輯可以看到 while
即將結束時, node
一定為 NULL
因此可以改成 if
即可
接著可以看到整個程式碼呼叫了非常多次的 malloc
,卻沒有任何的 free
,因此應該要在節點被 remove 的時候適當的釋放記憶體,並在程式最後把整個 hash table 釋放,以下為新增的部份