巧用「双指针」算法,轻松解决力扣题「19. 删除链表的倒数第 N 个结点」
2023-11-22 20:50:48
在编程的世界里,链表是一种常见的数据结构,它由一系列相互连接的结点组成,每个结点包含数据和指向下一个结点的指针。链表的优点在于它的灵活性,可以轻松地插入或删除结点,而不需要移动其他结点。
力扣题「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)。在实际应用中,我们可以根据具体情况选择合适的方法来解决问题。
希望这篇文章对您有所帮助。如果您有任何问题,请随时提出。