返回

巧用「双指针」算法,轻松解决力扣题「19. 删除链表的倒数第 N 个结点」

前端

在编程的世界里,链表是一种常见的数据结构,它由一系列相互连接的结点组成,每个结点包含数据和指向下一个结点的指针。链表的优点在于它的灵活性,可以轻松地插入或删除结点,而不需要移动其他结点。

力扣题「19. 删除链表的倒数第 N 个结点」考察了我们对链表操作的掌握程度。题目给定一个链表和一个整数 N,要求我们删除链表的倒数第 N 个结点,并返回链表的头结点。

对于这道题,我们可以使用多种方法来解决。下面,我们将详细介绍双指针算法、递归和迭代这三种方法,并对它们的优缺点进行比较。

双指针算法

双指针算法是一种巧妙的算法,它使用两个指针来遍历链表。第一个指针从链表的头结点开始,第二个指针从链表的第 N 个结点开始。然后,这两个指针同时向后移动,直到第二个指针到达链表的末尾。此时,第一个指针所在的结点就是我们要删除的结点。

def remove_nth_from_end(head, n):
  """
  删除链表的倒数第 N 个结点

  Args:
    head: 链表的头结点
    n: 要删除的倒数第 N 个结点

  Returns:
    链表的头结点
  """

  # 创建两个指针,一个从头结点开始,一个从链表的第 N 个结点开始
  first_pointer = head
  second_pointer = head
  for _ in range(n):
    second_pointer = second_pointer.next

  # 如果第二个指针到达了链表的末尾,则说明要删除的结点是头结点
  if second_pointer is None:
    return head.next

  # 否则,两个指针同时向后移动,直到第二个指针到达链表的末尾
  while second_pointer.next is not None:
    first_pointer = first_pointer.next
    second_pointer = second_pointer.next

  # 删除要删除的结点
  first_pointer.next = first_pointer.next.next

  # 返回链表的头结点
  return head

双指针算法的优点在于它的时间复杂度为 O(n),其中 n 为链表的长度。它的空间复杂度为 O(1),因为我们只需要使用两个指针来遍历链表。

递归

递归是一种常用的算法,它通过将问题分解成更小的子问题来解决。对于这道题,我们可以使用递归来找到要删除的结点,然后将其从链表中删除。

def remove_nth_from_end(head, n):
  """
  删除链表的倒数第 N 个结点

  Args:
    head: 链表的头结点
    n: 要删除的倒数第 N 个结点

  Returns:
    链表的头结点
  """

  # 如果链表为空或要删除的结点是头结点,则直接返回空链表
  if head is None or n == 0:
    return None

  # 递归地找到要删除的结点
  next_node = remove_nth_from_end(head.next, n - 1)

  # 如果要删除的结点不是头结点,则将其从链表中删除
  if n == 1:
    return next_node

  # 否则,返回链表的头结点
  head.next = next_node
  return head

递归的优点在于它的代码简洁明了。它的时间复杂度为 O(n),其中 n 为链表的长度。它的空间复杂度为 O(n),因为递归调用会使用额外的空间来存储调用栈。

迭代

迭代是一种常用的算法,它通过重复执行某个操作来解决问题。对于这道题,我们可以使用迭代来找到要删除的结点,然后将其从链表中删除。

def remove_nth_from_end(head, n):
  """
  删除链表的倒数第 N 个结点

  Args:
    head: 链表的头结点
    n: 要删除的倒数第 N 个结点

  Returns:
    链表的头结点
  """

  # 创建一个哑结点,并将它指向链表的头结点
  dummy_node = ListNode(0)
  dummy_node.next = head

  # 创建两个指针,一个指向哑结点,一个指向链表的头结点
  first_pointer = dummy_node
  second_pointer = head

  # 移动第一个指针,直到它与第二个指针相距 N 个结点
  for _ in range(n):
    first_pointer = first_pointer.next

  # 移动两个指针,直到第二个指针到达链表的末尾
  while second_pointer.next is not None:
    first_pointer = first_pointer.next
    second_pointer = second_pointer.next

  # 删除要删除的结点
  first_pointer.next = first_pointer.next.next

  # 返回链表的头结点
  return dummy_node.next

迭代的优点在于它的代码简洁明了。它的时间复杂度为 O(n),其中 n 为链表的长度。它的空间复杂度为 O(1),因为我们只需要使用两个指针来遍历链表。

比较

双指针算法、递归和迭代这三种方法各有优缺点。双指针算法的时间复杂度和空间复杂度都为 O(n),而递归和迭代的时间复杂度为 O(n),空间复杂度分别为 O(n) 和 O(1)。在实际应用中,我们可以根据具体情况选择合适的方法来解决问题。

希望这篇文章对您有所帮助。如果您有任何问题,请随时提出。