1. 程式人生 > >玩轉資料結構——第四章:連結串列和遞迴

玩轉資料結構——第四章:連結串列和遞迴

內容概要:

  1. Leetcode中和連結串列相關的問題
  2. 測試自己的Leetcode連結串列程式碼
  3. 遞迴繼承與遞迴的巨集觀語意
  4. 連結串列的天然遞迴結構性質
  5. 遞迴執行機制:遞迴的微觀解讀
  6. 遞迴演算法的除錯
  7. 更多和連結串列相關的問題

1-Leetcode中和連結串列相關的問題

題目:

刪除連結串列中等於給定的val的所有元素。

示例:

給定:1—>2—>6—>3—>4—>5—>6,val=6

返回:1—>2—>3—>4—>5

給定的ListNode類

public class ListNode {

    public int val;
    public ListNode next;

    public ListNode(int x) {
        val = x;
    }
}

問題分析:

  • 第一種解決方案:連結串列沒有設定虛擬頭結點

首先,判斷連結串列是否為空(head!=null)為空則返回head,不為空執行下面刪除操作

處理特殊位置,連結串列頭部的刪除工作:如果head是val值(head.val=val)

ListNode delNode=head;

head=head.next;

delNode.next=null;

然後刪除中間位置的方法:

定義prev為要刪除節點的前一個節點

ListNode prev=head;

prev.next下一個節點不為空則且prve.next.val==val則執行刪除操作

ListNode delNode=prev.next;//定義要刪除的節點

prev.next=delNode.next;

delNode.next=null;

否則,prev=prev.next;繼續往下找

class Solution {

    public ListNode removeElements(ListNode head, int val) {
        //1假設head節點的val等於給定的val
        //1解決頭結點的刪除
        while (head != null && head.val == val) {
            //定義要刪除的那個節點
            ListNode delNode = head;
            head = head.next;
            delNode.next = null;
        }
        //如果頭結點為空
        if (head == null)
            return null;

        //2解決中間部分的刪
        ListNode prev = head;//頭結點為下一個節點的前一個節點
        while (prev.next != null) {//下一個節點不為空
            if (prev.val == val) {///中間部分的某一個節點的val等於給定val
                ListNode delNode = prev.next;
                prev.next = delNode.next;
                delNode.next = null;
            } else {//prev往下找
                prev = prev.next;
            }
        }

        return head;

    }

}
  • 第二種解決方案:連結串列設定虛擬頭結點

在連結串列的頭部設定一個虛擬頭結點,則除了頭部刪除和中間刪除都是同一個操作

class Solution2 {

    public ListNode removeElements(ListNode head, int val) {
        ListNode dummyhead = new ListNode(-1);//設定虛擬節點,值任意
        dummyhead.next = head;//虛擬節點成頭結點

        //解決頭結點和中間部分的刪
        ListNode prev = dummyhead;//頭結點為下一個節點的前一個節點
        while (prev.next != null) {//下一個節點不為空
            if (prev.next.val == val) {///中間部分的某一個節點的val等於給定val
//                ListNode delNode = prev.next;
//                prev.next = delNode.next;
//                delNode.next = null;
                prev.next = prev.next.next;//和上面三句等效
            } else {//prev往下找
                prev = prev.next;
            }
        }
        return dummyhead.next;//虛擬頭結點不展示 
    }}

2-測試自己的Leetcode連結串列程式碼

在給定的ListNode類中,寫一個帶參建構函式,引數傳進來一個整形陣列,轉成連結串列

public class ListNode {

    public int val;
    public ListNode next;

    public ListNode(int x) {
        val = x;
    }

    //連結串列節點的建構函式
    //使用arr為引數。建立一個連結串列。當前的ListNode為連結串列的頭結點
    public ListNode(int[] arr) {
        if (arr == null || arr.length == 0)
            throw new IllegalArgumentException("arr cannot be empt");

        this.val = arr[0];
        ListNode cur = this;//this用來儲存cur的狀態 //裡面記錄的所以cur的狀態1->NUll /1->2->NULL/1->2->3-NULL....
        for (int i = 1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);//每次傳入新的引數生成新的物件,賦值給cur.next
            cur = cur.next;//將cur.next傳給cur作為新的cur的位置,傳遞
        }
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        ListNode cur = this;//this通過上面的方法村裡
        while (cur != null)
        {
            res.append(cur.val + "->");
            cur = cur.next;
        }
        res.append("NULL");
        return res.toString();
    }
}

在Solution中實現測試

 public  static  void main (String[] args){
        int[] nums={1,2,3,6,4,5,6};
        //利用帶參建構函式將陣列變成新的連結串列
        ListNode node=new ListNode(nums);
        System.out.println(node);

        ListNode newNode=new Solution().removeElements(node,6);
        System.out.println(newNode);
    }

結果:

1->2->3->6->4->5->6->NULL
1->2->3->4->5->NULL

3-遞迴繼承與遞迴的巨集觀語意

遞迴:本質上,將原來的問題,轉化為更小的同一問題

/**
 * 採用遞迴的方式來計算陣列的和
 */
public class Sum {
    public static int sum(int[] arr) {
        return sum(arr, 0);//遞迴的呼叫,從0到n-1的和
    }


    //真正的遞迴
    //計算arr[l,n)這個區間內所有數字的和
    protected static int sum(int[] arr, int l) {
        if (l == arr.length)
            return 0;
        //解決的問題規模變小了一層一層的呼叫(計算【l+1,n)的和...計算[n-1,n)的和)
        return arr[l] + sum(arr, l + 1);

    }}

測試函式: 結果36

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
        System.out.println(sum(arr));
    }

 

4-連結串列的天然遞迴結構性質 

用遞迴演算法的兩個步驟:

  • 1.先求解最基本問題
  • 2.把原問題轉化成更小的問題

用遞迴的演算法解決刪除連結串列元素中為e的值

/**
 * 使用遞迴解決刪除連結串列中為e的元素
 */
class Solution2 {

    public ListNode removeElements(ListNode head, int val) {
        //第一步:解決最基本的問題
        if (head == null)
            return null;
        head.next = removeElements(head.next, val);
        return head.val == val ? head.next : head;

    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 6, 4, 5, 6};
        //利用帶參建構函式將陣列變成新的連結串列
        ListNode node = new ListNode(nums);
        System.out.println(node);

        ListNode newNode = new Solution2().removeElements(node, 6);
        System.out.println(newNode);
    }

}

5-遞迴執行機制:遞迴的微觀解讀

如果不解決函式的基本問題(最後那一層,head==null),遞迴將永遠進行下去,直到佔滿系統棧的空間。

6-遞迴演算法的除錯

使用列印輸出的方式來理解從連結串列移除val元素的值的執行過程

/**
 * 使用列印輸出的方式來輸出連結串列移除元素val的值的執行過程
 */
class Solution2 {

    public ListNode removeElements(ListNode head, int val, int depth) {
        String depthString = generateDepthString(depth);
        System.out.print(depthString);
        System.out.println("Call:remove " + val + " in " + head);
        //第一步:解決最基本的問題
        if (head == null) {
            System.out.print(depthString);
            System.out.println("Return:remove " + head);
            return null;
        }

        ListNode res = removeElements(head.next, val, depth + 1);
        System.out.print(depthString);
        System.out.println("After:remove " + val + " in " + res);
        ListNode ret;//暫存
        if (head.val == val)
            ret = res;
        else
            head.next = res;
        ret = head;
        System.out.print(depthString);
        System.out.println("Return: " + ret);
        return ret;

    }

    private String generateDepthString(int depth) {
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            res.append("-|");
        }
        return res.toString();
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 6, 4, 5, 6};
        //利用帶參建構函式將陣列變成新的連結串列
        ListNode node = new ListNode(nums);
        System.out.println(node);

        ListNode newNode = new Solution2().removeElements(node, 6, 0);
        System.out.println(newNode);
    }

}

輸出結果: 

1->2->3->6->4->5->6->NULL
Call:remove 6 in 1->2->3->6->4->5->6->NULL
-|Call:remove 6 in 2->3->6->4->5->6->NULL
-|-|Call:remove 6 in 3->6->4->5->6->NULL
-|-|-|Call:remove 6 in 6->4->5->6->NULL
-|-|-|-|Call:remove 6 in 4->5->6->NULL
-|-|-|-|-|Call:remove 6 in 5->6->NULL
-|-|-|-|-|-|Call:remove 6 in 6->NULL
-|-|-|-|-|-|-|Call:remove 6 in null
-|-|-|-|-|-|-|Return:remove null
-|-|-|-|-|-|After:remove 6 in null
-|-|-|-|-|-|Return: 6->NULL
-|-|-|-|-|After:remove 6 in 6->NULL
-|-|-|-|-|Return: 5->6->NULL
-|-|-|-|After:remove 6 in 5->6->NULL
-|-|-|-|Return: 4->5->6->NULL
-|-|-|After:remove 6 in 4->5->6->NULL
-|-|-|Return: 6->4->5->6->NULL
-|-|After:remove 6 in 6->4->5->6->NULL
-|-|Return: 3->6->4->5->6->NULL
-|After:remove 6 in 3->6->4->5->6->NULL
-|Return: 2->3->6->4->5->6->NULL
After:remove 6 in 2->3->6->4->5->6->NULL
Return: 1->2->3->6->4->5->6->NULL
1->2->3->6->4->5->6->NULL

7-更多和連結串列相關的問題

 

(轉自發條魚)