插入第 N 個節點。系列目錄見 前言和目錄 。javascript
實現一個 insertNth()
方法,在鏈表的第 N 個索引處插入一個新節點。java
insertNth()
能夠當作是 01 Push & Build List 中的 push()
函數的更通用版本。給定一個鏈表,一個範圍在 0..length 內的索引號,和一個數據,這個函數會生成一個新的節點並插入到指定的索引位置,並始終返回鏈表的頭。node
insertNth(1 -> 2 -> 3 -> null, 0, 7) === 7 -> 1 -> 2 -> 3 -> null) insertNth(1 -> 2 -> 3 -> null, 1, 7) === 1 -> 7 -> 2 -> 3 -> null) insertNth(1 -> 2 -> 3 -> null, 3, 7) === 1 -> 2 -> 3 -> 7 -> null)
若是索引號超出了鏈表的長度,函數應該拋出異常。git
實現這個函數容許使用第一個 kata 中的 push
方法。github
讓咱們先回憶一下 push
函數的用處,指定一個鏈表的頭和一個數據,push
會生成一個新節點並添加到鏈表的頭部,並返回新鏈表的頭。好比:segmentfault
push(null, 23) === 23 -> null push(1 -> 2 -> null, 23) === 23 -> 1 -> 2 -> null
如今看看 insertNth
,假設函數方法簽名是 insertNth(head, index, data)
,那麼有兩種狀況:函數
若是 index === 0
,則等同於調用 push
。實現爲 push(head, data)
。測試
若是 index !== 0
,咱們能夠把下一個節點當成子鏈表傳入 insertNth
,並讓 index
減一。insertNth
的返回值必定是個鏈表,咱們把它賦值給 head.next
就行。這就是一個遞歸過程。若是此次遞歸的 insertNth
完不成任務,它會繼續遞歸到下一個節點,直到 index === 0
的最簡單狀況,或 head
爲空拋出異常(索引過大)。ui
完整代碼實現爲:code
function insertNth(head, index, data) { if (index === 0) return push(head, data) if (!head) throw 'invalid argument' head.next = insertNth(head.next, index - 1, data) return head }
若是能理解遞歸版本的 head.next = insertNth(...)
,那麼循環版本也不難實現。不一樣的是,在循環中咱們遍歷到 index
的前一個節點,而後用 push
方法生成新節點,並賦值給前一個節點的 next
屬性造成一個完整的鏈表。
完整代碼實現以下:
function insertNthV2(head, index, data) { if (index === 0) return push(head, data) for (let node = head, idx = 0; node; node = node.next, idx++) { if (idx + 1 === index) { node.next = push(node.next, data) return head } } throw 'invalid argument' }
這裏有一個邊界狀況要注意。由於 insertNth
要求返回新鏈表的頭。根據 index
是否爲 0
,這個新鏈表的頭多是生成的新節點,也可能就是老鏈表的頭 。這點若是寫進 for
循環就不可避免有 if/else
的返回值判斷。因此咱們把 index === 0
的狀況單獨拿出來放在函數頂部。這個邊界狀況並不是沒法歸入循環中,咱們下面介紹的一個技巧就與此有關。
在以前的幾個 kata 裏,咱們提到循環能夠更好的容納邊界狀況,由於一些條件判斷都能寫到 for
的頭部中去。但這個例子的邊界狀況是返回值不一樣:
若是 index === 0
,返回新節點 。
若是 index !== 0
,返回 head 。新節點會被插入 head 以後的某個節點鏈條中。
如何解決這個問題呢,咱們能夠在 head
前面再加入一個節點(數據任意,通常賦值 null)。這個節點稱爲 dummy 節點。這樣一來,無論新節點插入到哪裏,dummy.next
均可以引用到修改後的鏈表。
代碼實現以下,注意 return
的不一樣。
function insertNthV3(head, index, data) { const dummy = push(head, null) for (let node = dummy, idx = 0; node; node = node.next, idx++) { if (idx === index) { node.next = push(node.next, data) return dummy.next } } throw 'invalid argument' }
dummy 節點是不少鏈表操做的經常使用技巧,雖然在這個 kata 中使用 dummy 節點的代碼量並無變少,但這個技巧在後續的一些複雜 kata 中會很是有用。