程式碼隨想錄-day1

2023-03-01 21:01:31

連結串列

今天主要是把連結串列專題刷完了,連結串列專題的題目不是很難,基本都是考察對連結串列的操作的理解。

在處理連結串列問題的時候,我們通常會引入一個哨兵節點(dummy),dummy節點指向原連結串列的頭結點。這樣,當我們對頭結點進行操作的時候就可以直接使用dummy節點,不用進行特判。

在對連結串列進行操作的時候 while的迴圈條件也是容易犯錯的地方,我們不應該死記這題該是cur != null還是cur.next != null又或是其他。而是應該畫個圖,手動模擬一下,便知道結束的條件。

203.移除連結串列元素

題意:刪除連結串列中等於給定值 val 的所有節點。

範例 1:
輸入:head = [1,2,6,3,4,5,6], val = 6
輸出:[1,2,3,4,5]

範例 2:
輸入:head = [], val = 1
輸出:[]

範例 3:
輸入:head = [7,7,7,7], val = 7
輸出:[]

思路

遍歷列表找到要刪除的節點的前一個節點,修改該節點的指標跳過要刪除的節點。

關鍵在於,處理頭結點和如何找到要刪除的前一個節點。我們可以使用一個哨兵節點和pre指標來實現。

pre初始值為哨兵節點,cur初始值是頭結點。這樣每次pre和cur都向後移一位即可,判斷如果cur等於要刪除的節點就讓pre = cur.next,迴圈的條件為cur != null

程式碼

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode dummy = new ListNode();
        dummy.next = head;
        ListNode pre = dummy;
        ListNode cur = head;

        while (cur != null) {
            if (cur.val == val) {
                pre.next = cur.next;
            } else {
                pre = cur;
            }
            cur = cur.next;
        }

        return dummy.next;
    }
}

707.設計連結串列

題意:

在連結串列類中實現這些功能:

  • get(index):獲取連結串列中第 index 個節點的值。如果索引無效,則返回-1。
  • addAtHead(val):在連結串列的第一個元素之前新增一個值為 val 的節點。插入後,新節點將成為連結串列的第一個節點。
  • addAtTail(val):將值為 val 的節點追加到連結串列的最後一個元素。
  • addAtIndex(index,val):在連結串列中的第 index 個節點之前新增值為 val 的節點。如果 index 等於連結串列的長度,則該節點將附加到連結串列的末尾。如果 index 大於連結串列長度,則不會插入節點。如果index小於0,則在頭部插入節點。
  • deleteAtIndex(index):如果索引 index 有效,則刪除連結串列中的第 index 個節點。

思路

本題不是一道演演算法題,是一道考察對連結串列的功能的具體實現的設計題。

熟悉連結串列具體操作即可,同樣的我們引入哨兵節點和一個儲存連結串列大小的size變數會方便我們的操作。

程式碼

class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val = val;
    }
}

class MyLinkedList {

    int size; // 連結串列長度
    ListNode head; // 虛擬頭結點

    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }
    
    public int get(int index) {
        if (index < 0 || index >= size) return -1;
        ListNode cur = head; 
        // 包含一個虛擬頭結點,所以要<=
        for (int i = 0; i <= index; i ++ ) {
            cur = cur.next;
        }
        return cur.val;
    }
    
    public void addAtHead(int val) {
        addAtIndex(0, val);
        
    }
    
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }
    
    public void addAtIndex(int index, int val) {
        if (index > size) return;
        if (index < 0) index = 0;

        size ++;
        ListNode pre = head; 
        // 包含一個虛擬頭結點,所以要<=
        for (int i = 0; i < index; i ++ ) {
            pre = pre.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = pre.next;
        pre.next = toAdd;
    }
    
    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) return ;
        size --;

        if (index == 0) {
            head = head.next;
            return ;
        }
        
        ListNode pre = head;
        for (int i = 0; i < index; i ++ ) {
            pre = pre.next;
        }
        pre.next = pre.next.next;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

206.反轉連結串列

題意:反轉一個單連結串列。

範例: 輸入: 1->2->3->4->5->NULL 輸出: 5->4->3->2->1->NULL

思路

本題我們可以從題目要求入手,對連結串列進行一個模擬。不難看出我們需要將後一個節點指向前一個節點 ,在此基礎上我們還要解決一個問題:我們以樣例來說明,當我們將節點2指向節點1時,我們應該怎麼移動當前指標到下一個節點。

顯然,我們可以定義一個變數next來預先儲存節點2的next指標即可。另一個問題是如何讓節點1指向null,參考之前說過的操作引入一個哨兵節點,這樣pre指標指向哨兵節點,cur指標指向頭結點。在最開始我們就能讓頭節點指向null。

程式碼

1.雙指標寫法

class Solution {
    public ListNode reverseList(ListNode head) {
        // 複習,非遞迴寫法
        ListNode pre = null;
        ListNode cur = head;

        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }

        return pre;
    }
}

2.遞迴寫法

class Solution {
    public ListNode reverseList(ListNode head) {
        return reverse(head, null);
    }
    // 關於cur為什麼為空 , 因為我們返回的是pre,如果cur == null 時 pre才指向最後一個連結串列的元素
    public ListNode reverse(ListNode cur, ListNode pre) {
        if (cur == null) return pre;

        ListNode next = cur.next;
        cur.next = pre;
        return reverse(next, cur);
    }
}

24. 兩兩交換連結串列中的節點

題意:給定一個連結串列,兩兩交換其中相鄰的節點,並返回交換後的連結串列。你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。

範例:

思路

拿到一道題目我們看完題乾和資料範圍後,一定先自己動手模擬一下,理清楚題目要求,也能方便我們將模擬操作抽象為具體的程式碼。

下面是根據樣例我們模擬的過程:

在圖片中上面的連結串列是初始時的連結串列,下面的連結串列為我們的目標連結串列。

可以看出如果要得到目標連結串列我們必須得進行三個步驟:

  1. 將哨兵節點指向節點2
  2. 將節點2指向節點1
  3. 將節點1指向節點3
    在我們完成上面三個步驟後,我們將當前指標移動兩位,及如果我們要操作兩個節點,指標必須在這兩個節點的前面一個節點。

但是當我們在改變指向以後我們就不能找到節點1和節點3了,所以我們得提前儲存一下節點1和節點3,由此可知迴圈的條件為cur.next != null && cur.next.next != null

根據以上思路不難寫出程式碼。

程式碼

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode cur = dummy;

        while (cur.next != null && cur.next.next != null) {
            ListNode tmp = cur.next;
            ListNode tmp1 = cur.next.next.next;

            cur.next = cur.next.next;
            cur.next.next = tmp;
            cur.next.next.next = tmp1;

            cur = cur.next.next;
        }

        return dummy.next;
    }
}

19.刪除連結串列的倒數第N個節點

題意:給你一個連結串列,刪除連結串列的倒數第 n 個結點,並且返回連結串列的頭結點。

進階:你能嘗試使用一趟掃描實現嗎?

範例:

思路

首先,最暴力的思路就是遍歷一遍統計連結串列長度,再遍歷一遍刪除倒數第N個節點,這種思路的程式碼實現簡單,我們不在此給出。

我們考慮如何使用一趟掃描實現此功能,在這裡我們引入一個快慢指標的思想。

我們設定兩個指標他們的起點都為哨兵節點,其中快指標fast每次先移動n位,然後快慢指標才一起開始移動,當快指標到達連結串列末尾的時候,慢指標剛好指向我們要刪除的倒數第N個節點的前一個節點。

如下圖所示:

所以迴圈的結束條件為fast走到最後時,及cur.next != null

程式碼

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode fast = dummy;
        ListNode slow = dummy;

        while (n -- > 0) fast = fast.next;

        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        slow.next = slow.next.next;

        return dummy.next;
    }
}

160.連結串列相交

題意:給你兩個單連結串列的頭節點 headA 和 headB ,請你找出並返回兩個單連結串列相交的起始節點。如果兩個連結串列沒有交點,返回 null 。

範例:圖示兩個連結串列在節點 c1 開始相交:

題目資料 保證 整個鏈式結構中不存在環。

注意,函數返回結果後,連結串列必須 保持其原始結構 。

範例 1:

範例 2:

範例 3:

思路

首先本題不是比較的val值相同,而是比較的節點相同,及val和next都相同。

明確了這一點之後我們繼續討論如何解決該問題,由於給出的兩個連結串列可能長度並不相同,所以我們應該讓兩個連結串列從相同長度的位置開始進行比較。

所以我們先得到兩個連結串列的長度,再讓長的連結串列先前進兩個連結串列之間的差值的距離。

這樣我們就可以從相同位置開始進行比較,當比較到兩個節點相同的時候,即找到了相交的節點返回即可,當迴圈結束時還沒有找到則沒有相交的節點。

while迴圈的條件不難得出為cur != null

程式碼

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;

        int lenA = 0, lenB = 0;
        int len;

        while (curA != null) {
            lenA ++;
            curA = curA.next;
        }

        while (curB != null) {
            lenB ++;
            curB = curB.next;
        }

        // 找出長度大的那個連結串列
        curA = headA;
        curB = headB;
        if (lenB > lenA) {
            int tmp = lenA;
            lenA = lenB;
            lenB = tmp;
            curA = headB;
            curB = headA;
        }
        // 讓長的先走
        len = lenA - lenB;
        while (len -- > 0) curA = curA.next;

        while (curA != null) {
            if (curA == curB) return curA;
            curA = curA.next;
            curB = curB.next;
        }

        return null;
    }
}

142.環形連結串列II

題意:給定一個連結串列,返回連結串列開始入環的第一個節點。 如果連結串列無環,則返回 null。

為了表示給定連結串列中的環,使用整數 pos 來表示連結串列尾連線到連結串列中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該連結串列中沒有環。

說明:不允許修改給定的連結串列。

思路

首先要解決題目要求的問題,我們可以將其轉換為解決兩個子問題

子問題1:然後判斷有環

在這裡我們也引入一個快慢指標,快指標每次走兩步,慢指標每次走一步。當連結串列中存在環的時候,快指標和慢指標一定會相遇。

子問題2:如何找到環的起點

根據子問題1得到的相遇的節點,我們用index表示,同時我們定義一個index1在頭結點的位置,然後讓兩個節點一起走,當他們相遇的時候的節點即為環開始的節點。

具體證明請看:程式碼隨想錄

while 迴圈的條件為fast != null && fast.next != null

程式碼

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;

        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;

            if (slow == fast) {
                ListNode idx = fast;
                ListNode idx1 = head;
                while (idx != idx1) {
                    idx = idx.next;
                    idx1 = idx1.next;
                }
                return idx;
            }
        }
        return null;
    }
}