鏈表常見操作
OK, 八個空格, 我們繼續學習鏈表常見操作. 接下來主要分爲兩個部分的內容, 第一個部分, 總結鏈表中修改指針的題目; 第二部分, 講環形鏈表! 可能第二個部分比較有乾貨.
鏈表問題的特性 - 修改指針
鏈表的問題實際上, 就是修改指針走向的問題. 很多時候多多畫圖, 用幾個臨時變量ListNode就可以解決了. 即使想要新建一個鏈表成本也很低, 改改指針複用之前的元素就好了. 當然, 這樣講太粗了, 等下會給出幾個題目. 當然, 這也是因爲筆者偷懶, 不知道這些題目應該歸屬到那些類型裏面去. 要注意的是, 我們經常會將指針修改後, 繼續使用, 從而導致錯誤. 例如
reverse正常版本
public ListNode reverse(ListNode head) {
ListNode prev = null;
ListNode tmp = head;
while(tmp != null) {
ListNode next = tmp.next;
tmp.next = prev; // 記得先保存, 再修改
prev = tmp;
tmp = next;
}
return prev;
}
錯誤版本
public ListNode reverse(ListNode head) {
ListNode prev = null;
ListNode tmp = head;
while(tmp != null) {
tmp.next = prev;
prev = tmp;
tmp = tmp.next;
}
return prev;
}
應對口訣: 先保存, 再修改. 一般會形成一個環.
然後, 是修改指針有關題目: 兩兩翻轉 , K個一組翻轉 .
環操作
如果沒有環操作, 那這個系列就不完整了.
判斷是否有環的存在
環形鏈表 主要通過fast-slow指針去完成.
查找環入口元素
環形鏈表 II 需要一些理論知識 - Floyd 算法 .解析如下:
首先, 假設環的長度爲C, 非環部分的長度爲F. 再設入口節點的下標是0, 相應的head節點的下標是-F, tail節點的下標是C-1.
上圖轉自leet-code
階段一
slow=head;
fast=head;
現在開始fast-slow迭代. 當slow走到環的入口時, 迭代進行了F次; 而fast相對於slow多走了F步, 到達下標爲h的點. 對於h, 我們有結論一: h = F mod C.(這是一個重要的結論,後面會用到. )
由於相對速度, fast和slow在C-h次迭代之後相遇. 此時, fast位於下標爲 h+2(C-h) = 2C-h的點, 即C-h點.
階段二
再設置一個指針從頭開始與fast進行同步迭代, 兩者每次行進的步長爲1.
在F次迭代之後, 第一個指針到達入口; 而fast,位於C-h+F處, 由於結論一, fast也位於入口. 兩者相等.
相關代碼如下:
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next !=null) {
slow = slow.next;
fast = fast.next.next;
if (fast == slow) {
ListNode tmp = head;
while (tmp!=fast) {
tmp = tmp.next;
fast = fast.next;
}
return tmp;
}
}
return null;
}