鏈表與遞歸
鏈表元素刪除問題的解答
問題描述:在鏈表[1, 2, 6, 3, 4, 5, 6] 中刪除值爲 6 的元素
-
ListNode.java
結構說明public class ListNode { int val; ListNode next; ListNode(int x) {val = x;} /** * 鏈表節點構造函數,自定義 * @param arr */ ListNode(int[] arr) { if (arr == null || arr.length == 0) { throw new IllegalArgumentException("arr can not be empty"); } this.val = arr[0]; ListNode cur = this; for (int i = 1; i < arr.length; i++) { cur.next = new ListNode(arr[i]); cur = cur.next; } } @Override public String toString() { StringBuilder sb = new StringBuilder(); ListNode cur = this; while (cur != null) { sb.append(cur.val).append(" -> "); cur = cur.next; } sb.append("NULL"); return sb.toString(); } }
-
常規方式,對鏈表的三個部分[頭、中、尾]進行分別處理
public ListNode removeElement(ListNode head, int val) { // 鏈表頭部節點刪除 while (head != null && head.val == val) head = head.next; // 鏈表尾部節點刪除 if (head == null) { return null; } // 鏈表中間部分節點刪除 ListNode prev = head; while (prev.next != null) { if (prev.next.val == val) prev.next = prev.next.next; else prev = prev.next; } return head; } }
-
虛擬頭結點方式,使得每個鏈表節點均含有前置節點,改進代碼
public ListNode removeElement(ListNode head, int val) { // 建立虛擬頭結點,保證鏈表中每一個節點前面均有節點 ListNode dummyHead = new ListNode(-1); dummyHead.next = head; // 鏈表節點刪除 ListNode prev = dummyHead; while (prev.next != null) { if (prev.next.val == val) prev.next = prev.next.next; else prev = prev.next; } return dummyHead.next; }
-
測試一下!
public static void main(String[] args) { int[] arr = {1, 2, 6, 3, 4, 5, 6}; ListNode res = new ListNode(arr); System.out.println(res); new Solution2().removeElement(res, 6); System.out.println(res); } ------------------------------------------ 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6 -> NULL 1 -> 2 -> 3 -> 4 -> 5 -> NULL
遞歸:計算機中的很重要的組件邏輯機制
-
本質上,將原來的問題,轉化爲更小的同一問題,比如數組求和!
SUM(arr[0...n-1]) = arr[0] + SUM(arr[1...n-1])
SUM(arr[1...n-1]) = arr[1] + SUM(arr[2...n-1])
SUM(arr[n-1...n-1]) = arr[n-1] + SUM(arr[]) = arr[n-1] + 0
-
遞歸算法的組成
求解最基本的問題
把原問題轉化成更小的問題
-
鏈表具有遞歸性質
- 鏈表可以理解爲多個節點的連接體,也可以看做
一個節點和一個鏈表
的連接體。 - 而
NULL
也是是一個最基本的鏈表。 - 爲了方便理解,畫了一張圖!
- 根據圖,我們可以改寫代碼!
public ListNode removeElementNew(ListNode head, int val) { // 基礎問題 one if (head == null) { return null; } // 處理子鏈表,分解問題 two head.next = removeElementNew(head.next, val); // 處理結果,若當前返回子鏈表滿足條件,便跳過節點 three return head.val == val ? head.next : head; }
- 舉個栗子,現在有鏈表
1, 2, 3
,想要刪除元素2
上述方法是怎麼執行的呢?step1
入參【1, 2, 3】 以 1 爲頭結點的鏈表one
head != nulltwo
head.next = ? ,進入第一次遞歸,step2
step2
入參【2, 3】 以 2 爲頭結點的鏈表one
head != nulltwo
head.next = ?,進入第二次遞歸,step3
step3
入參【3】以3 爲頭結點的鏈表one
head != nulltwo
head.next = ? ,進入第三次遞歸,step4
step4
入參【NULL】,NULL 鏈表one
head == null,返回null
,基本問題出現了!!!
step5
回到step3 two
two
head.next = 【null】three
head.val == 2? head.next : head- return head ,此時鏈表爲【3】,回到
step2 two
step6
回到step2 two
two
head.next = 【3】three
head.val == 2? head.next : head,此時條件滿足,爲true
- return head.next,此時鏈表爲【3】,回到
step1 two
step7
回到step1 two
two
head.next = 【3】three
head.val == 2? head.next : head- return head,此時鏈表爲【1, 3】,回到
step1
,已經執行完了one
、two
、three
,方法返回,結束。
- 鏈表可以理解爲多個節點的連接體,也可以看做
-
遞歸調用時有代價的:函數調用 + 系統棧空間(記錄當前執行位置、變量狀態、時間消耗),若不處理基礎問題,即沒有遞歸出口,方法執行一直佔內存,直到內存佔滿,或溢出,導致系統over了。一個算法必須總是在有限次的執行後結束,且每一步都能在有限時間內完成。