leetcode高頻題筆記之鏈表

鏈表的題目都比較死,幾種類型反覆做反覆記憶就好了

環形鏈表

在這裏插入圖片描述
解法一:記錄查找法

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
        next = null;
    }
}

public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<ListNode>();
        ListNode node = head;
        while (node != null) {

            if (set.contains(node)) {
                return true;
            }

            set.add(node);
            node = node.next;
        }

        return false;
    }
}

解法二:快慢指針法(背下來就好了,真的想不到)

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
        next = null;
    }
}

public class Solution {
    public boolean hasCycle(ListNode head) {

        if (head == null || head.next == null) {
            return false;
        }

        ListNode fast = head.next;
        ListNode slow = head;

        while (fast != slow) {
            if (fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}

環形鏈表II

在這裏插入圖片描述
解法一:hash

public class Solution {
    public ListNode detectCycle(ListNode head) {

        Set<ListNode> set = new HashSet<ListNode>();
        ListNode cur = head;
        while (cur != null) {
            if (set.contains(cur)) {
                return cur;
            }
            set.add(cur);
            cur = cur.next;
        }
        return null;
    }
}

解法二:快慢指針法
在這裏插入圖片描述
思路:

  • 先和環形鏈表I一樣判斷有沒有環,有才進行第二步
  • 從同一起點開始,fast一次走兩步,slow一次走一步,當他們相遇之後,fast回到原點一次走一步,第二次相遇節點就是環連接點
  • 我的理解:
    • 就假設最簡單的第二圈發生第一次相遇,第二圈走完了就第二次相遇了
    • 第一次相遇時fast走了F+(a+b)+a
    • slow走了F+a
    • 因爲fast是slow的兩倍速度,所以第二次相遇時fast=2*slow
    • 第二次相遇slow走了F+a+b
    • 那麼fast要走2F+2a+2b,還差F+b
    • 所以就假設fast以一倍的速度陪slow走完b,另一倍速從F出發,當fast與slow相遇時,相當於fast走過了b+F
public class Solution {
    public ListNode detectCycle(ListNode head) {

        ListNode fast = head;
        ListNode slow = head;
        while (true) {
            if (fast == null || fast.next == null) return null;
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;

        }
        fast = head;
        //第二階段,找環接入點
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}

合併兩個有序鏈表

在這裏插入圖片描述
要點:保留一個pre標記,讓一個新的指針去遍歷

public class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {

        ListNode pre = new ListNode(0);
        ListNode cur = pre;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                cur.next = l1;
                cur = cur.next;
                l1 = l1.next;
            } else {
                cur.next = l2;
                cur = cur.next;
                l2 = l2.next;
            }
        }
        //如果l1空了,就把l2接上去
        if (l1 == null) {
            cur.next = l2;
        } else {
            cur.next = l1;
        }
        return pre.next;
    }
}

反轉鏈表

在這裏插入圖片描述

迭代法:原地反轉

在這裏插入圖片描述

public class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null){
            ListNode tmp = cur.next;
            cur.next  = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
}

遞歸:
在這裏插入圖片描述

public class Solution {
    public ListNode reverseList(ListNode head) {
        //遞歸終止條件
        if (head == null || head.next == null) {
            return head;
        }
        //下探
        ListNode cur = reverseList(head.next);
        
        head.next.next = head;
        head.next = null;
        //每一次都返回最後一個節點
        return cur;
    }
}

圖片引自leetcode題解

兩兩交換鏈表中的節點

在這裏插入圖片描述

解法一:遞歸

public class Solution {
    //每次看人家寫遞歸都覺得自己像個傻子,看得懂想不到
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        ListNode next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;
        return next;
    }
}

解法二:迭代

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode node = new ListNode(-1);
        node.next = head;
        ListNode pre = node;
        while (pre.next != null && pre.next.next != null) {
            ListNode l1 = pre.next, l2 = pre.next.next;

            ListNode next = l2.next;
            l1.next = next;
            l2.next = l1;
            pre.next = l2;

            pre = l1;
        }
        return node.next;
    }
}

相交鏈表

在這裏插入圖片描述
在這裏插入圖片描述
本題的要點就是交替遍歷兩條路
在這裏插入圖片描述
已上圖爲例,一條路徑遍歷a-c-b,另一條遍歷b-c-a
當兩條路徑遍歷相遇時,就是交點

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        if (headA == null || headB == null) return null;

        ListNode node1 = headA;
        ListNode node2 = headB;

        while (node1 != node2) {
            node1 = node1 == null ? headB : node1.next;
            node2 = node2 == null ? headA : node2.next;
        }
        return node1;
    }
}

刪除排序鏈表中的重複元素

在這裏插入圖片描述
迭代實現:

	public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode node = head;
        while (node.next != null && node != null) {

            if (node.val == node.next.val){
                node.next = node.next.next;
            }else {
                node = node.next;
            }
        }
        return head;
    }

遞歸實現:

	public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) return head;
        head.next = deleteDuplicates(head.next);
        return head.val == head.next.val ? head = head.next : head;
    }

刪除鏈表的倒數第N個節點

在這裏插入圖片描述
一趟掃描:快慢指針法

public class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {

        if (head == null || head.next == null) return null;

        ListNode fast = head;
        ListNode slow = head;

        //將fast和slow的步調差調成n,當fast到達末尾了,slow到達指定節點
        while (n > 0) {
            fast = fast.next;
            n--;
        }
        //倒數的數就是頭結點的情況,此時fast是null
        if (fast == null){
            return head.next;
        }

        //倒數的數不是頭結點,按順序移動fast和slow節點,直到倒數第二個,測試slow節點到達刪除節點的上一個
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        //slow是待刪除節點上一個節點,刪除slow.next節點
        slow.next = slow.next.next;

        return head;

    }
}

兩數相加II

在這裏插入圖片描述

  • 遍歷鏈表,將值分別壓入棧中
  • 獲取的棧頂元素就是同一位的元素了,如果有就直接出棧獲取,如果沒有就爲0
  • 計算兩個值與進位數的和爲sum
  • 讓sum對10求模得本位的數
  • 讓sum對10整除得進位數
  • 用本位數創建一個節點插入到鏈表頭(pre.next)
public class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //初始化stack
        Stack<Integer> stack1 = buildStack(l1);
        Stack<Integer> stack2 = buildStack(l2);
        //新建個前驅結點方便返回
        ListNode pre = new ListNode(-1);
        int carry = 0;//進位數
        while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
            int x = stack1.isEmpty() ? 0 : stack1.pop();
            int y = stack2.isEmpty() ? 0 : stack2.pop();
            int sum = x + y + carry;
            ListNode node = new ListNode(sum % 10);//計算出本位的值
            //將node插入到頭結點
            node.next = pre.next;
            pre.next = node;
            //計算是否有進位數
            carry = sum / 10;
        }
        return pre.next;
    }

    private Stack<Integer> buildStack(ListNode head) {
        ListNode node = head;
        Stack<Integer> stack = new Stack<>();
        while (node != null) {
            stack.push(node.val);
            node = node.next;
        }
        return stack;
    }
}

迴文鏈表

在這裏插入圖片描述
O(n) 時間複雜度和 O(1) 空間複雜度解法:

  • 快慢指針找到中點
  • 選擇一半進行翻轉
  • 翻轉後再次比較
public class Solution {
    /**
     * 快慢指針找到中點
     * 選擇一半進行翻轉
     * 翻轉後再次比較
     */
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) return true;

        ListNode slow = head;
        ListNode fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        if (fast != null) slow = slow.next;//偶數個結點,讓slow指向下一個,作爲後半段的開頭
        //如果爲奇數剛好過了中點,就是後半段的開頭
        //將鏈表分爲以head和slow開頭的等長的兩段
        cut(head, slow);
        return isEqual(head, reverse(slow));
    }

    //翻轉鏈表
    private ListNode reverse(ListNode head) {
        ListNode newHead = null;
        while (head != null) {
            ListNode tmp = head.next;
            head.next = newHead;
            newHead = head;
            head = tmp;
        }
        return newHead;
    }

    //判定兩個鏈表是否相等
    private boolean isEqual(ListNode head1, ListNode head2) {
        while (head1 != null && head2 != null) {
            if (head1.val != head2.val) return false;
            head1 = head1.next;
            head2 = head2.next;
        }
        return true;
    }

    //分割鏈表
    private void cut(ListNode head, ListNode slow) {
        while (head.next != slow) {
            head = head.next;
        }
        head.next = null;
    }
}

奇偶鏈表

在這裏插入圖片描述
思路:奇數放一個鏈表,偶數放一個鏈表,最後兩個鏈表連接起來

public class Solution {
    public ListNode oddEvenList(ListNode head) {

        if (head == null || head.next == null) return head;

        ListNode odd = head;//奇數節點
        ListNode even = head.next;//偶數節點
        ListNode evenHead = even;//暫存一個偶數節點頭,方便重組完進行連接

        while (even != null && even.next != null) {
            odd.next = even.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        //奇數鏈表後接上偶數鏈表
        odd.next = evenHead;
        return head;

    }
}

分割鏈表

在這裏插入圖片描述
思路:

  • 統計鏈表長度
  • 長度小於等於k,每個子鏈表放一個節點
  • 長度大於k,計算每個子鏈表長度,分割鏈表
public class Solution {
    public ListNode[] splitListToParts(ListNode root, int k) {
        if (root == null) return new ListNode[k];
        ListNode cur = root;
        int count = 0;
        //統計節點數
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        ListNode[] nodes = new ListNode[k];

        cur = root;
        //每個子鏈表不超過1個節點
        if (count <= k) {
            for (int i = 0; i < count; i++) {
                nodes[i] = new ListNode(cur.val);
                cur = cur.next;
            }
        } else {
            //計算每個子鏈表的節點數
            int remain = count % k;
            int preCount = count / k;

            //記錄每部分的節點個數
            int[] counts = new int[k];
            for (int i = 0; i < k; i++) {
                //前remain個鏈表長度爲precount+1
                counts[i] = remain-- > 0 ? preCount + 1 : preCount;
            }
            //遍歷鏈表,存儲元素
            for (int i = 0; i < k; i++) {
                //初始化子鏈表的頭結點和個數
                int num = counts[i];
                nodes[i] = cur;

                while (--num > 0) {
                    cur = cur.next;
                }

                //截斷鏈表
                ListNode tmp = cur.next;
                cur.next = null;
                cur = tmp;

            }


        }
        return nodes;

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章