Day04~24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、160.链表相交、142.环形链表II

115 阅读6分钟

摘要

本文主要介绍了几个LeetCode链表相关的题目,涵盖了 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、160.链表相交、142.环形链表II 等题目。最后,ChatGPT为链表专题进行了总结。

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

1.1 思路

链表操作模拟题 遍历链表cur表示当前节点,设置p1, p2分表代表需要交换的第一个节点和第二个节点 分为三步,p1.next = p2.next, p2.next = p1, cur.next = p2

1.2 代码

    // 链表操作模拟题
    // 遍历链表cur表示当前节点,设置p1, p2分表代表需要交换的第一个节点和第二个节点
    // 分为三步,p1.next = p2.next, p2.next = p1, cur.next = p2
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(-1, head);
        ListNode cur = dummy;
        ListNode p1, p2;
​
        while ((p1 = cur.next) != null && (p2 = p1.next) != null) {
            ListNode next = p1;
​
            p1.next = p2.next;
            p2.next = p1;
            cur.next = p2;
​
            cur = next;
        }
        return dummy.next;
    }

2、19.删除链表的倒数第N个节点

2.1 思路

链表操作模拟题 1.计算链表的长度;2.获取倒数第n个节点的头节点;3.删除节点

注意事项

  • 设置虚拟节点,因为删除头节点需要使用虚拟节点

  • 获取倒数第n个节点的头节点中,getNode(dummy, (len - 1) - n)

    • 因为有虚拟节点,len - 1 才是链表的长度
    • 可以代入示例,如 head = [1,2,3,4,5], n = 2 ,判断是否正确

2.2 代码

    // 链表操作模拟题
    // 1.计算链表的长度;2.获取倒数第n个节点的头节点;3.删除节点
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(-1, head);
​
        // 1.计算链表的长度
        int len = getLength(dummy);
​
        // 2.获取倒数第n个节点的头节点
        ListNode prev = getNode(dummy, (len - 1) - n);
​
        // 3.删除节点
        prev.next = prev.next.next;
        return dummy.next;
    }
​
    public int getLength(ListNode head) {
        ListNode cur = head;
        int len = 0;
​
        while (cur != null) {
            len++;
            cur = cur.next;
        }
        return len;
    }
​
    public ListNode getNode(ListNode head, int index) {
        ListNode cur = head;
​
        while (index > 0 && cur != null) {
            index--;
            cur = cur.next;
        }
        return cur;
    }

3、160.链表相交

3.1 思路

双指针,同时遍历headA、headB,curA和curB分别代表它们的当前节点 headA遍历完时,curA指向headB;headB遍历完时,curB指向headA 如果headA与headB相交,curA=curB=相交点,反之直至结束,此时curA==curB==null 因为headA=diffA+common,headB=diffB+common,则满足 headA + diffB = headB + diffA

3.2 代码

    // 双指针,同时遍历headA、headB,curA和curB分别代表它们的当前节点
    // headA遍历完时,curA指向headB;headB遍历完时,curB指向headA
    // 如果headA与headB相交,curA=curB=相交点,反之直至结束,此时curA==curB==null
    // 因为headA=diffA+common,headB=diffB+common,则满足 headA + diffB = headB + diffA
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
​
        while (curA != curB) {
            curA = (curA != null) ? curA.next : headB;
            curB = (curB != null) ? curB.next : headA;
        }
        return curA;
    }

4、142.环形链表II

4.1 思路

1、如何想到快慢指针?

如果链表无环,快慢指针不肯能相遇;而且快指针每次移动2步,一个节点一个节点的去靠近慢指针,如果链表有环,快慢指针一定会再环中相遇。

2、如何找到环的入口?

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。

推到公式如下:

  • 第一次相遇时:2 (x + y ) = x + y + n (y + z)
  • 数学公式推导:x + y = n (y + z)
  • 数学公式推导:x = (n -1) (y + z) + z
  • 数学公式推导:x = z

所以当slow,fast第一次相遇后,定义两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口

4.2 代码

error-1

错误的代码

       ...
       if(fast == null) {
            return null;
       }
       ...

正确的代码

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

如何判断是链表无环跳出循环还是slow==fast跳出循环的情况?

只用判断fast和fast.next就像,因为快指针fast永远走在慢指针slow的前面,fast不为空slow一定不为空,fast或fast.next为空一定是链表无环,并且因为快指针fast一次走2步(fast=fast.next.next),所以也需要判断fast.next不能为空

error-2

错误的代码

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

正确的代码

        while(fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
​
            if(slow == fast) {
                break;
            }
        }

错误的原因

  1. 快指针fast比慢指针slow快,只需要判断fast指针即可
  2. 错误的代码中(fast = fast.next.next) != null,并没有判断fast.next为空的情况

AC

    // 快慢指针,遍历链表,定义slow,fast即快慢指针
    // 当slow,fast第一次相遇后,定义两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
    
        // 第一次相遇
        while(fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
​
            if(slow == fast) {
                break;
            }
        }
        if(fast == null || fast.next == null) {
            return null;
        }
​
        // 第二次相遇
        ListNode idx1 = head;
        ListNode idx2 = slow;
        while(idx1 != idx2) {
            idx1 = idx1.next;
            idx2 = idx2.next;
        }
        return idx1;        
    }

5、总结

链表是数据结构中的一个重要部分,以下是关于链表的一些LeetCode题目以及它们的总结:

  1. 203. 移除链表元素

    这道题目要求移除链表中所有值等于给定值的节点。可以使用迭代或递归来处理。遍历链表,如果节点的值等于给定值,就跳过这个节点。

  2. 707. 设计链表

    这是一道设计题,要求实现一个单链表的基本操作,包括插入、删除、获取等。需要特别注意边界条件和链表为空的情况。

  3. 206. 反转链表

    这个问题要求反转一个单链表。可以使用迭代或递归来解决。迭代的方法需要使用三个指针来反转节点的指向关系,递归的方法则递归地反转每个节点。

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

    这是一道要求交换链表中相邻节点的题目。可以使用迭代或递归来解决。迭代方法需要注意节点的连接顺序,递归方法则递归地处理相邻的节点。

  5. 19. 删除链表的倒数第N个节点

    这个问题要求删除链表中倒数第N个节点。可以使用快慢指针来解决。快指针先走N步,然后快慢指针一起走,当快指针到达链表末尾时,慢指针指向的节点就是要删除的节点。

  6. 160. 链表相交

    这个问题要求判断两个链表是否相交,并找到它们的交点。可以使用双指针法来解决。分别从两个链表的头节点开始,遍历到末尾后跳到另一个链表的头节点继续遍历,直到两个指针相遇或同时到达末尾。

  7. 142. 环形链表 II

    这个问题要求找到环形链表的入口节点。使用快慢指针来判断是否有环,然后从头节点和相遇点同时开始遍历,直到两个指针相遇,相遇点就是环的入口。

这些题目涵盖了链表的基本操作和一些高级问题,对于理解和掌握链表数据结构非常有帮助。可以通过练习这些题目来提高链表操作的熟练度。

参考资料

代码随想录-两两交换链表中的节点

代码随想录-删除链表的倒数第N个节点

代码随想录-链表相交

代码随想录-环形链表II