返回

LeetCode 234. 回文链表:评估链表的回文特性

前端

回文链表:三种解决方法剖析

在数据结构的世界中,回文链表是一个颇具趣味且实用的难题。回文链表是指一个链表从头到尾读和从尾到头读是相同的,就像一个单词或句子中的回文一样。判断一个给定的链表是否为回文链表是计算机科学领域一个常见的问题,它不仅在理论研究中具有价值,在实际应用中也大有可为。

本文将深入探讨三种解决回文链表问题的常见方法:使用额外空间、使用快慢指针,以及使用递归。每种方法都有其独特的优点和适用场景,了解它们的原理和应用场景至关重要。

方法一:使用额外空间(数组/栈)

思路:

这种方法相对简单直接。首先,我们将链表中的所有元素依次存储到一个数组或栈中。然后,从数组或栈的末尾开始,依次与链表中的元素进行比较。如果所有元素都相等,则链表是回文的,否则不是。

代码示例(使用数组):

def is_palindrome(head):
  arr = []  # 存储链表元素的数组
  while head:
    arr.append(head.val)
    head = head.next

  for i in range(len(arr) // 2):
    if arr[i] != arr[len(arr) - i - 1]:
      return False

  return True

优点:

  • 实现简单,易于理解。
  • 时间复杂度为 O(n),其中 n 为链表的长度。

缺点:

  • 需要额外的空间存储链表元素,空间复杂度为 O(n)。
  • 当链表长度较大时,可能会耗费较多的空间和时间。

方法二:使用快慢指针

思路:

使用快慢指针是一种巧妙的方法,不需要额外的空间。我们使用两个指针,一个快指针每次移动两步,一个慢指针每次移动一步。当快指针到达链表尾部时,慢指针刚好到达链表中部。然后,我们从慢指针开始,逆序遍历链表的后半部分,并与前半部分进行比较。如果所有元素都相等,则链表是回文的,否则不是。

代码示例:

def is_palindrome(head):
  slow = head
  fast = head
  while fast and fast.next:
    slow = slow.next
    fast = fast.next.next

  # 逆序链表的后半部分
  prev = None
  while slow:
    next = slow.next
    slow.next = prev
    prev = slow
    slow = next

  # 比较链表的前半部分和后半部分
  while head and prev:
    if head.val != prev.val:
      return False
    head = head.next
    prev = prev.next

  return True

优点:

  • 无需额外空间,节省内存。
  • 时间复杂度为 O(n),其中 n 为链表的长度。

缺点:

  • 实现相对复杂,需要对指针操作有较好的理解。
  • 对于链表长度较长的情况,逆序链表的后半部分可能会耗费较多的时间。

方法三:使用递归

思路:

递归是一种优雅的方法,可以将问题分解成更小的子问题。我们将链表分成两部分,然后递归地判断这两部分是否都是回文的。如果这两部分都是回文的,那么整个链表就是回文的。否则,链表不是回文的。

代码示例:

def is_palindrome_helper(head, tail):
  if head == tail:
    return True

  if head.next == tail:
    return head.val == tail.val

  return is_palindrome_helper(head.next, tail.prev) and head.val == tail.val

def is_palindrome(head):
  if not head or not head.next:
    return True

  return is_palindrome_helper(head, None)

优点:

  • 实现简洁,代码量少。
  • 时间复杂度为 O(n),其中 n 为链表的长度。

缺点:

  • 递归调用可能会导致栈空间溢出,对于链表长度较长的情况不适用。
  • 理解递归思想需要一定的基础。

选择最优方法

这三种方法各有千秋,在不同场景下可以发挥其优势。

  • 对于链表长度较短或需要节省内存的情况,使用快慢指针方法是一个不错的选择。
  • 对于链表长度较长且不需要考虑内存限制的情况,可以使用额外空间的方法。
  • 对于需要递归思维或代码简洁的情况,可以使用递归方法。

常见问题解答

1. 如何判断一个循环链表是否为回文链表?

对于循环链表,可以使用快慢指针的方法,但需要对快指针的移动规则稍作修改。当快指针绕过整个循环链表一圈后,慢指针刚好位于循环链表的中点。

2. 如果链表中有奇数个元素,如何使用递归方法判断回文链表?

如果链表中有奇数个元素,我们可以让递归函数返回中间元素。然后,分别判断中间元素的前半部分和后半部分是否都是回文的。

3. 如何在不修改原链表的情况下判断回文链表?

可以使用栈或队列来存储链表元素,然后从后往前与原链表元素进行比较。

4. 如何判断一个链表是否是回文子链表?

我们可以先找到链表的中间点,然后判断中间点前后两部分是否都是回文的。

5. 如何优化回文链表的判断算法?

我们可以利用回文链表的对称性,只比较链表的一半。如果前半部分和后半部分都相等,那么整个链表就是回文的。