2. 链表问题梳理

117 阅读5分钟

参考:

  1. 如何 K 个一组反转链表

整理了以下几类问题:

  • 反转链表
  • 判断链表的环
  • 链表加法
  • 链表合并
  • 链表分离
  • 查询元素
  • 删除元素
  • 其他:排序、相交、回文、拷贝

问题列表

微信图片_20230526100327.jpg

序号题目完成
206. 反转链表
92. 反转链表 II
24. 两两交换链表中的节点
25. K 个一组翻转链表
141. 环形链表
142. 环形链表 II
面试题 02.05. 链表求和
445. 两数相加 II
21. 合并两个有序链表
剑指 Offer 25. 合并两个排序的链表
23. 合并K个排序链表
86. 分隔链表
328. 奇偶链表
143. 重排链表
61. 旋转链表
剑指 Offer 22. 链表中倒数第k个元素
83. 删除排序链表中的重复元素
82. 删除排序链表中的重复元素 II
160. 相交链表
剑指 Offer 52. 两个链表的第一个公共节点
234. 回文链表
138. 复制带随机指针的链表

解题思路

206. 反转链表

// 递归写法
class Solution {
    // 递归函数的定义为:将链表反转并返回反转后第一个节点
    public ListNode reverseList(ListNode head) {
        // 这其实就是找到最后一个节点
        if (head == null || head.next == null) {
            return head;
        }
        // 比如第一次反转,newHead就是5, 5432 -> 1
        ListNode newHead = reverseList(head.next);
        // 此时不能拿newHead指向head,必须要用head.next指向head
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}
// 递归写法
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode p = null, q = head;
        while (q != null) {
            // 执行反转操作
            ListNode r = q.next;
            q.next = p;

            p = q;
            q = r;
        }
        return p;
    }
}

92. 反转链表 II

注意:头尾为空的时候需要特殊处理!!!

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode l1 = null;
        ListNode l2 = head;
        ListNode l3 = head;
        ListNode l4 = null;
        while (left > 1) {
            l1 = l2;
            l2 = l2.next;
            left--;
        }
        while (right > 1) {
            l3 = l3.next;
            right--;
        }
        // 记录事后需要接上的部分
        l4 = l3.next;
        // 将中间需要反转的链表的尾部断开,不然无法反转
        l3.next = null;

        // 将中间部分翻转
        l2 = revert(l2);
        // 拼接前面的部分
        if (l1 != null) {
            // 如果前面不为空,还是使用原来的head作为开头
            l1.next = l2;
        } else {
            // 如果前面为空,返回new2作为新的开头
            head = l2;
        }
        // 拼接后面的部分
        if (l4 != null) {
            while (l2.next != null) {
                l2 = l2.next;
            }
            l2.next = l4;
        }
        return head;
    }

    public ListNode revert(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = revert(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}
//leetcode submit region end(Prohibit modification and deletion)

25. K 个一组翻转链表

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode a = head;
        ListNode b = head;
        for (int i = 0; i < k; i++) {
            // base条件
            if (b == null) {
                return head;
            }
            b = b.next;
        }
        ListNode pre = revert(a, b);
        a.next = reverseKGroup(b, k);
        return pre;
    }

    // 翻转[a,b)
    public ListNode revert(ListNode a, ListNode b) {
        ListNode pre = null;
        ListNode cur = a;
        while (cur != b) {
            ListNode nxt = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }
}

445. 两数相加 II

class Solution {
    // 将链表反转来计算
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode new1 = revert(l1);
        ListNode new2 = revert(l2);
        ListNode ans = addTwo(new1, new2);
        return revert(ans);
    }

    public ListNode addTwo(ListNode l1, ListNode l2) {
        int carry = 0;
        ListNode head = new ListNode();
        ListNode p = head;
        while (l1 != null || l2 != null || carry != 0) {
            int v1 = l1 != null ? l1.val : 0;
            int v2 = l2 != null ? l2.val : 0;
            int v = v1 + v2 + carry;
            carry = v / 10;
            head.next = new ListNode(v % 10);
            head = head.next;

            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }
        return p.next;
    }

    public ListNode revert(ListNode root) {
        if (root == null || root.next == null) {
            return root;
        }

        ListNode head = revert(root.next);
        root.next.next = root;
        root.next = null;
        return head;
    }
}

141. 环形链表

判断链表是否有环。

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast!=null && fast == slow){
                return true;
            }
        }
        return false;
    }
}

142. 环形链表 II

返回环的入口。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        ListNode intersection = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast != null && fast == slow) {
                //相遇
                while (intersection != fast) {
                    intersection = intersection.next;
                    fast = fast.next;
                }
                return intersection;
            }
        }
        return null;
    }
}

21. 合并两个有序链表

// 迭代写法
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        ListNode p = new ListNode();
        ListNode head = p;
        while(list1!=null && list2!=null){
            if(list1.val < list2.val){
                p.next = list1;
                list1 = list1.next;
            }else{
                p.next = list2;
                list2 = list2.next;
            }
            p = p.next;
        }
        if(list1!=null){
            p.next = list1;
        }
        if(list2!=null){
            p.next = list2;
        }
        return head.next;
    }
}

递归写法要会写!!!

// 递归写法
class Solution {
    // 将两个有序链表合并,并返回第一个链表
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null){
            return l2;
        }else if(l2 == null){
            return l1;
        }else if(l1.val < l2.val){
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

23. 合并K个排序链表

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) {
            return null;
        }
        // 用一个堆来确定每次取哪个链表
        PriorityQueue<ListNode> minQueue = new PriorityQueue<ListNode>((o1, o2) -> {
            return o1.val - o2.val;
        });
        ListNode p = new ListNode();
        ListNode head = p;
        for (ListNode l : lists) {
            if (l != null) {
                minQueue.add(l);
            }
        }
        while (!minQueue.isEmpty()) {
            ListNode min = minQueue.poll();
            p.next = min;
            if (min.next != null) {
                minQueue.add(min.next);
            }
            p = p.next;
        }
        return head.next;
    }
}

分治法要会写!!!

// 分治法
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        return merge(lists, 0 , lists.length-1);
    }

    public ListNode merge(ListNode[] lists, int l , int r){
        if(l == r){
            return lists[l];
        }
        if(l > r){
            return null;
        }
        int mid = (l+r) >> 1;
        return mergeTwo(merge(lists, l ,mid), merge(lists, mid+1 ,r));
    }

    public ListNode mergeTwo(ListNode list1, ListNode list2){
        if(list1 == null){
            return list2;
        }else if(list2 == null){
            return list1;
        }else if(list1.val < list2.val){
            list1.next = mergeTwo(list1.next, list2);
            return list1;
        }else{
            list2.next = mergeTwo(list1, list2.next);
            return list2;
        }
    }
}

面试题 02.05. 链表求和

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int carry = 0;
        ListNode head = new ListNode();
        ListNode p = head;
        while (l1 != null || l2 != null || carry != 0) {
            int v1 = l1 != null ? l1.val : 0;
            int v2 = l2 != null ? l2.val : 0;
            int v = v1 + v2 + carry;
            carry = v / 10;
            head.next = new ListNode(v % 10);
            head = head.next;

            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }
        return p.next;
    }
}

86. 分隔链表

class Solution {
    public ListNode partition(ListNode head, int x) {
        ListNode lower = new ListNode(0);
        ListNode lowerHead = lower;
        ListNode higher = new ListNode(0);
        ListNode higherHead = higher;
        ListNode p = head;
        while (p != null) {
            if (p.val < x) {
                lower.next = p;
                lower = lower.next;
            } else {
                higher.next = p;
                higher = higher.next;
            }
            ListNode next = p.next;
            // 这个题目的关键就在于此,如果要做到原地分割,那么就得把临时节点的next断开,不然会产生环
            p.next = null;
            p = next;
        }
        lower.next = higherHead.next;
        return lowerHead.next;
    }
}

328. 奇偶链表

class Solution {
    public ListNode oddEvenList(ListNode head) {
        // 空链表返回null
        if (head == null) {
            return null;
        }

        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;
    }
}

143. 重排链表

要注意的点就是,要拿中点后面那个节点。

为什么?因为合并链表的逻辑,是从l1开始,一次操作是↓↗,第二个例子,4会直接指向空,所以我们应该保证l1大于等于l2。

如下两个链表合并的结果:

1->2->3
5->4
得到:1->5->2->4->3

1->2
5->4->3
得到:1->5->2->4

题解:

class Solution {
    public void reorderList(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        ListNode pre = null;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            pre = slow;
            slow = slow.next;
        }
        ListNode l2 = slow.next;
        // 将第二个序列反转
        l2 = revert(l2);
        slow.next = null;

        // 合并
        mergeTwo(head, l2);
    }

    public ListNode revert(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = revert(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }

    public void mergeTwo(ListNode l1, ListNode l2) {
        ListNode l1_tmp;
        ListNode l2_tmp;
        while (l1 != null && l2 != null) {
            l1_tmp = l1.next;
            l2_tmp = l2.next;

            l1.next = l2;
            l1 = l1_tmp;

            l2.next = l1;
            l2 = l2_tmp;
        }
    }
}

剑指 Offer 22. 链表中倒数第k个元素

相同问题:面试题 02.02. 返回倒数第 k 个节点

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode p = head;
        while (p != null && k > 0) {
            p = p.next;
            k--;
        }
        ListNode ans = head;
        while (p != null) {
            p = p.next;
            ans = ans.next;
        }
        return ans;
    }
}

61. 旋转链表

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        // base情况,链表为空或者k为0
        if (head == null || k == 0) {
            return head;
        }
        // 先计算出链表的长度
        int size = 1;
        ListNode tail = head;
        while (tail != null && tail.next != null) {
            tail = tail.next;
            size++;
        }
        // k可能会大于链表长度,所以先取模,比如长度5,k为7和k为2的结果是一样的
        k = k % size;
        // k和长度相等,此时翻转的结果就是自身
        if (k == 0) {
            return head;
        }

        // 找到倒数第K个元素
        ListNode tmp = head;
        while (tmp != null && k > 0) {
            tmp = tmp.next;
            k--;
        }
        ListNode l1 = head;
        // 倒数第K个元素的前一个元素,目的是为了断开前部分链表,避免产生环
        ListNode pre = null;
        while (tmp != null) {
            tmp = tmp.next;
            pre = l1;
            l1 = l1.next;
        }

        if (pre != null) {
            // 将链表从中间断开
            pre.next = null;
        }

        // 重新连接起来
        tail.next = head;
        return l1;
    }
}

83. 删除排序链表中的重复元素

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        // 因为已经排好序了,可以用双指针解决
        ListNode pre = null;
        ListNode cur = head;
        ListNode ans = head;
        while (cur != null) {
            ListNode next = cur.next;
            // 相等的时候,删除cur,此时pre不需要移动
            if (pre != null && pre.val == cur.val) {
                pre.next = next;
            } else {
                // 不删除的时候正常往前走
                pre = cur;
            }
            cur = next;
        }
        return ans;
    }
}

82. 删除排序链表中的重复元素 II

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return head;
        }

        ListNode dummy = new ListNode(0, head);

        ListNode cur = dummy;
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                int x = cur.next.val;
                while (cur.next != null && cur.next.val == x) {
                    cur.next = cur.next.next;
                }
            } else {
                cur = cur.next;
            }
        }

        return dummy.next;
    }
}

160. 相交链表

相同题目:剑指 Offer 52. 两个链表的第一个公共节点

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
}

24. 两两交换链表中的节点

两种解法都要掌握!!!

class Solution {
    // 递归解法
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode newHead = head.next;
        head.next = swapPairs(newHead.next);
        newHead.next = head;
        return newHead;
    }
}
class Solution {
    // 迭代解法
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0, head);
        ListNode tmp = dummy;
        while (tmp.next != null && tmp.next.next != null) {
            ListNode node1 = tmp.next;
            ListNode node2 = tmp.next.next;
            tmp.next = node2;
            node1.next = node2.next;
            node2.next = node1;
            tmp = node1;
        }
        return dummy.next;
    }
}

234. 回文链表

class Solution {
    public boolean isPalindrome(ListNode head) {
        // 找到中间节点,将后一个链表翻转
        ListNode slow = head;
        ListNode fast = head;
        ListNode pre = null;
        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            pre = slow;
            slow = slow.next;
        }
        if(pre!=null){
            pre.next = null;
        }
        
        // 然后比较两个链表是否相等
        ListNode l2 = revert(slow);
        return equals(head, l2);
    }

    public ListNode revert(ListNode head){
        if(head == null || head.next ==null){
            return head;
        }
        ListNode newHead = revert(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }

    public boolean equals(ListNode l1, ListNode l2){
        while(l1!=null){
            if(l1.val != l2.val){
                return false;
            }
            l1 = l1.next;
            l2 = l2.next;
        }
        return true;
    }
}

[随机算法]382. 链表随机节点