反转链表 II

标签: 链表

难度: Medium

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

 

示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1
输出:[5]

 

提示:

  • 链表中节点数目为 n
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

 

进阶: 你可以使用一趟扫描完成反转吗?

Submission

运行时间: 28 ms

内存: 15.1 MB

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
        if head is None:
            return head
        dummy = ListNode(next=head)
        pre = dummy
        a = b = head
        for _ in range(left-1):
            pre = pre.next
            a = a.next
        for _ in range(right):
            b = b.next

        def reverse(a, b):
            pre = None
            cur = a
            while cur != b:
                tmp = cur.next
                cur.next = pre
                pre = cur
                cur = tmp
            return pre

        pre.next = reverse(a, b)
        a.next = b
        return dummy.next

Explain

题解的思路是先找到需要反转的子链表的起始节点和结束节点,然后将这一段子链表反转,最后将反转后的子链表重新连接到原链表中。具体步骤如下: 1. 创建一个虚拟头节点 dummy,指向原链表的头节点。 2. 定义两个指针 pre 和 a,初始时都指向 dummy。 3. 先将 pre 和 a 向右移动 left-1 次,使得 a 指向需要反转的子链表的前一个节点。 4. 定义指针 b,初始时指向 a,然后将 b 向右移动 right-left+1 次,使得 b 指向需要反转的子链表的后一个节点。 5. 调用 reverse 函数反转 a 和 b 之间的子链表。 6. 将反转后的子链表重新连接到原链表中,即将 pre.next 指向反转后的子链表头节点,将 a.next 指向 b。 7. 返回 dummy.next,即反转后的链表的头节点。

时间复杂度: O(right)

空间复杂度: O(1)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
        if head is None:
            return head
        # 创建虚拟头节点 dummy,指向原链表的头节点
        dummy = ListNode(next=head)
        # 定义指针 pre 和 a,初始时都指向 dummy
        pre = dummy
        a = head
        # 将 pre 和 a 向右移动 left-1 次,使得 a 指向需要反转的子链表的前一个节点
        for _ in range(left-1):
            pre = pre.next
            a = a.next
        # 定义指针 b,初始时指向 a,然后将 b 向右移动 right-left+1 次,使得 b 指向需要反转的子链表的后一个节点
        b = a
        for _ in range(right-left+1):
            b = b.next
        # 定义反转链表的函数 reverse
        def reverse(a, b):
            pre = None
            cur = a
            while cur != b:
                tmp = cur.next
                cur.next = pre
                pre = cur
                cur = tmp
            return pre
        # 调用 reverse 函数反转 a 和 b 之间的子链表
        pre.next = reverse(a, b)
        # 将反转后的子链表重新连接到原链表中
        a.next = b
        # 返回反转后的链表的头节点
        return dummy.next

Explore

在函数reverse中,b指针指向需要反转的链表部分之后的第一个节点。在反转过程中,不包括b指针所指的节点,而是将a到b(不包含b)之间的节点进行反转。这意味着b是反转区间的边界外的第一个节点。

当left为1时,意味着反转从头节点开始。在这种情况下,pre和a初始位置仍然从虚拟头节点dummy开始。由于left-1为0,因此pre和a不会移动,pre会停留在dummy上,而a会指向头节点。这保证了即使从头节点开始反转,链表结构也能正确处理,且连续性不受影响。

在reverse函数执行后,a.next直接连接到b的原因是因为b指针在移动过程中已经考虑了其可能到达链表尾部之后的None位置。当right等于链表长度时,b会移动到链表尾部的next,即None。因此,a.next连接到b(无论是某个具体节点还是None)是安全的,这确保了链表在反转部分后能正确地接上剩余部分或正常结束。

在反转链表的过程中,确保不会丢失任何节点的关键在于使用pre和cur两个指针来重新设置节点的next指针,从而改变节点间的连接方向。在每次循环中,我们将当前节点cur的next指针指向前一个节点pre,然后更新pre和cur的位置。这种方式不仅改变了节点间的指向,还保持了遍历过程中所有节点的连续性。此外,最终将反转区域的前一节点(pre节点)的next指向反转后的头节点,以及反转区域的原始头节点(反转后的尾节点)的next指向b,确保了链表的完整性。