链表专题练习

113 阅读7分钟
  1. 反转链表(Reverse Linked List)
  2. 合并两个有序链表(Merge Two Sorted Lists)
  3. 删除链表中的倒数第 n 个节点(Remove Nth Node From End of List)
  4. 判断链表是否有环(Linked List Cycle)
  5. 找出链表的中间节点(Middle of the Linked List)
  6. 交换链表中相邻的节点(Swap Nodes in Pairs)
  7. 旋转链表(Rotate List)
  8. 链表排序(Sort List)
  9. 复制含有随机指针节点的链表(Copy List with Random Pointer
  10. 判断两个链表是否相交(Intersection of Two Linked Lists)
//反转链表
 class Solution {
    
        public ListNode reverseList(ListNode head) {
        
            ListNode prev = null; // 记录当前节点的前一个节点
            ListNode curr = head; // 记录当前节点
            while (curr != null) { // 遍历链表,直到链表末尾
                ListNode nextTemp = curr.next; // 记录当前节点的后一个节点
                curr.next = prev; // 将当前节点的 next 指向前一个节点

                prev = curr; // 更新前一个节点为当前节点
                curr = nextTemp; // 更新当前节点为下一个节点
            }

            return prev; // 返回新链表的头节点
        }
    }

这里使用了迭代的方式来反转链表,时间复杂度为 O(n),空间复杂度为 O(1)。具体来说,我们使用两个指针 prev 和 curr 来记录当前节点和前一个节点,然后遍历链表,每次将当前节点的 next 指向前一个节点,然后更新 prev 和 curr 指针即可。最后返回新链表的头节点 prev 即可。

//合并两个有序链表

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0); // 创建一个 dummy 节点,作为新链表的头节点
        ListNode curr = dummy; // 创建一个指针 curr,指向新链表的末尾节点

        while (l1 != null && l2 != null) { // 遍历两个链表,直到其中一个链表为空
            if (l1.val < l2.val) { // 如果 l1 的值小于 l2 的值
                curr.next = l1; // 将 curr 的 next 指向 l1
                l1 = l1.next; // 更新 l1 指针
            } else { // 如果 l1 的值大于等于 l2 的值
                curr.next = l2; // 将 curr 的 next 指向 l2
                l2 = l2.next; // 更新 l2 指针
            }

            curr = curr.next; // 更新 curr 指针
        }

        if (l1 != null) { // 如果 l1 不为空,将 l1 的剩余部分添加到新链表的末尾
            curr.next = l1;
        }

        if (l2 != null) { // 如果 l2 不为空,将 l2 的剩余部分添加到新链表的末尾
            curr.next = l2;
        }

        return dummy.next; // 返回新链表的头节点
    }
}

//删除倒数第n个节点
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0); // 创建虚拟头节点
        dummy.next = head; // 将虚拟头节点的下一个节点指向 head

        ListNode slow = dummy; // 创建慢指针
        ListNode fast = dummy; // 创建快指针

        for (int i = 0; i < n; i++) { // 快指针先移动 n 步
            fast = fast.next;
        }

        while (fast.next != null) { // 同时移动慢指针和快指针,直到快指针到达链表的末尾
            slow = slow.next;
            fast = fast.next;
        }

        slow.next = slow.next.next; // 删除倒数第 n 个节点

        return dummy.next; // 返回虚拟头节点的下一个节点
    }
}
/**链表是否有环
初始时,slow 和 fast 都指向链表的头节点。然后,slow 指针每次向后移动一个节点,fast 指针每次向后移动两个节点。如果链表中存在环,那么 fast 指针迟早会追上 slow 指针,此时就可以判断出链表中存在环。
**/
public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) {
        return false;
    }
    ListNode slow = head;
    ListNode fast = head.next;
    while (slow != fast) {
        if (fast == null || fast.next == null) {
            return false;
        }
        slow = slow.next;
        fast = fast.next.next;
    }
    return true;
}

slow 和 fast 都指向链表的头节点。然后,slow 指针每次向后移动一个节点,
fast 指针每次向后移动两个节点。当 fast 指针到达链表的末尾时,slow 指针就指向了链表的中间节点。

如果链表的长度是偶数,那么根据题目要求,中间节点应该是第 n/2+1 个节点

public ListNode middleNode(ListNode head) {
    if (head == null) {
        return null;
    }
    ListNode slow = head;
    ListNode fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

交换链表中相邻的节点,可以使用迭代或递归的方法实现。下面分别给出两种方法的具体实现。

方法一:迭代

我们可以使用迭代的方法,每次交换相邻的两个节点,直到遍历完整个链表。具体做法是,创建一个虚拟头节点 dummy,令 dummy.next = head,然后创建指针 p 指向 dummy,每次交换 p 后面的两个节点,直到 p 后面没有足够的节点可以交换。交换相邻节点的过程可以按照如下步骤实现:

  1. 令节点 a 指向 p.next;
  2. 如果 a 为 null,则跳出循环;
  3. 令节点 b 指向 a.next;
  4. 交换节点 a 和节点 b 的位置,并更新 p 的指向;
  5. 将 p 指向节点 a。

下面是 Java 代码实现:

public ListNode swapPairs(ListNode head) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode p = dummy;
    while (p.next != null && p.next.next != null) {
        ListNode a = p.next;
        ListNode b = a.next;
        a.next = b.next;
        b.next = a;
        p.next = b;
        p = a;
    }
    return dummy.next;
}

在代码中,我们首先创建虚拟头节点 dummy,令 dummy.next = head。然后,我们创建指针 p 指向 dummy,开始交换相邻节点。在每次交换过程中,我们先判断 p 后面是否有足够的节点可以交换,如果没有,就跳出循环。否则,我们令节点 a 指向 p.next,节点 b 指向 a.next,然后交换节点 a 和节点 b 的位置,并更新 p 的指向,将 p 指向节点 a。最后,返回 dummy.next,即为交换相邻节点后的链表。

方法二:递归

我们也可以使用递归的方法来实现链表中相邻节点的交换。具体做法是,先交换链表中的前两个节点,然后递归地交换后面的节点,直到遍历完整个链表。交换相邻节点的过程可以按照如下步骤实现:

  1. 令节点 a 指向 head;
  2. 如果 a 为 null 或 a.next 为 null,则返回 a;
  3. 令节点 b 指向 a.next;
  4. 令节点 c 指向 b.next;
  5. 交换节点 a 和节点 b 的位置,即令 a.next = swapPairs(c),b.next = a;
  6. 返回节点 b。

下面是 Java 代码实现:

public ListNode swapPairs(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode a = head;
    ListNode b = a.next;
    ListNode c = b.next;
    a.next = swapPairs(c);
    b.next = a;
    return b;
}

在代码中,我们首先判断 head 是否为空或 head.next 是否为空,如果是,就返回 head。否则,我们令节点 a 指向 head,节点 b 指向 a.next,节点 c 指向 b.next。然后,我们交换节点 a 和节点 b 的位置,即令

链表是否相交

我们分别遍历两个链表,记录它们的长度。然后,令两个指针 pA 和 pB 分别指向两个链表的头节点,并让它们同时向后移动,当其中一个指针到达链表尾部时,将它重定向到另一个链表的头节点。这样,当两个指针所指的节点相同时,说明它们相交;否则,它们不相交。时间复杂度为 O(m+n),空间复杂度为 O(1),其中 m 和 n 分别为两个链表的长度。

下面是 Java 代码实现:

public boolean isIntersect(ListNode headA, ListNode headB) {
    ListNode pA = headA;
    ListNode pB = headB;
    while (pA != pB) {
        pA = (pA == null) ? headB : pA.next;
        pB = (pB == null) ? headA : pB.next;
    }
    return pA != null;
}

在代码中,我们分别遍历两个链表,记录它们的长度。然后,令两个指针 pA 和 pB 分别指向两个链表的头节点,并让它们同时向后移动,当其中一个指针到达链表尾部时,将它重定向到另一个链表的头节点。当两个指针所指的节点相同时,说明它们相交;否则,它们不相交。最后,我们可以根据 pA 是否为空来判断它们是否相交,因为如果两个链表不相交,它们最终会同时到达链表的尾部,此时 pA 和 pB 都会变成 null。

这种解法的思路是利用了两个链表的长度差,让两个指针走过相同的距离,从而在相交节点处相遇。它的优点是在不需要遍历两个链表的情况下就可以判断它们是否相交,因此时间复杂度更低,而且不需要额外的空间来存储哈希表。