用 in-place 的方式把一個鏈表的首節點移到另外一個鏈表(不改變鏈表的引用),系列目錄見 前言和目錄 。javascript
實現一個 moveNode()
函數,把源鏈表的頭結點移到目標鏈表的開頭。要求是不能修改兩個鏈表的引用。java
var source = 1 -> 2 -> 3 -> null var dest = 4 -> 5 -> 6 -> null moveNode(source, dest) source === 2 -> 3 -> null dest === 1 -> 4 -> 5 -> 6 -> null
當碰到如下的狀況應該拋出異常:node
源鏈表爲 null
git
目標鏈表爲 null
github
源鏈表是空節點,data
屬性爲 null
的節點定義爲空節點。算法
跟 前一個 kata 不一樣的是,這個 kata 是在不改變引用的狀況下修改兩個鏈表自身。所以 moveNode()
函數不須要返回值。同時這個 kata 也提出了 空節點 的概念。空節點會用於目標鏈表爲空的狀況(爲了保持引用),在函數執行以後,目標鏈表會由空節點變成一個包含一個節點的鏈表。segmentfault
你可使用 第一個 kata 的 push
方法。數據結構
這個算法考的是對鏈表節點的插入和刪除。基本只對 source 和 dest 分別作一次操做,因此不用區分遞歸和循環。大體思路爲:函數
對 source
作刪除一個節點的操做。若是隻有一個節點就直接置空。若是有多個節點,就把第二個節點的值賦給頭節點,而後讓頭結點指向第三個節點。測試
對 dest
作插入一個節點的操做。若是頭結點爲空就直接賦值,不然把頭結點複製一份,做爲第二個節點插入到鏈表中,再把新值賦給頭結點。
代碼以下:
function moveNode(source, dest) { if (!source || !dest || source.data === null) throw new Error("invalid arguments") const data = source.data if (source.next) { source.data = source.next.data source.next = source.next.next } else { source.data = null } if (dest.data === null) { dest.data = data } else { dest.next = new Node(dest.data, dest.next) dest.data = data } }
這是我最開始思考的方案,差異在於對 dest
如何插入新節點的處理上用了遞歸。思路是把全部節點的 data
日後移一位,即把新值賦給第一個節點,第一個節點的值賦給第二個節點,第二個節點的值賦給第三個節點,以此類推。但實際操做中的順序必須是反的,就是把倒數第二個節點的值賦給最後一個節點,倒數第三個節點的值賦給倒數第二個節點…… 這個思路對 dest
操做了 N 次,不如上一個解法的 1 次操做高效。不過也算是個有意思的遞歸用例,因此我仍然把它放了上來。
代碼以下,主要看 pushInPlaceV2
:
function moveNodeV2(source, dest) { if (source === null || dest === null || source.isEmpty()) { throw new Error('invalid arguments') } pushInPlaceV2(dest, source.data) if (source.next) { source.data = source.next.data source.next = source.next.next } else { source.data = null } } function pushInPlaceV2(head, data) { if (!head) return new Node(data) if (!head.isEmpty()) head.next = pushInPlaceV2(head.next, head.data) head.data = data return head }
老是使用遞歸會產生慣性,致使忽略了數據結構的基本特性。鏈表的特性就是插入和刪除的便利,改改引用就成了。
算法相關的代碼和測試我都放在 GitHub 上,若是對你有幫助請幫我點個贊!