【算法思想·链表】如何判断回文链表

本文参考labuladong算法笔记[如何判断回文链表 | labuladong 的算法笔记]

1、前言

首先,寻找回文串是从中间向两端扩展,判断回文串是从两端向中间收缩。对于单链表,无法直接倒序遍历,可以造一条新的反转链表,可以利用链表的后序遍历,也可以用栈结构倒序处理单链表。

具体到回文链表的判断问题,由于回文的特殊性,可以不完全反转链表,而是仅仅反转部分链表,将空间复杂度降到 O(1)。

# 在 s 中寻找以 s[left] 和 s[right] 为中心的最长回文串
def palindrome(s: str, left: int, right: int) -> str:
    # 防止索引越界
    while left >= 0 and right < len(s) and s[left] == s[right]:
        # 双指针,向两边展开
        left -= 1
        right += 1
    # 返回以 s[left] 和 s[right] 为中心的最长回文串
    return s[(left + 1):right]

因为回文串长度可能为奇数也可能是偶数,长度为奇数时只存在一个中心点,而长度为偶数时存在两个中心点,所以上面这个函数需要传入 l 和 r

判断一个字符串是不是回文串就简单很多,不需要考虑奇偶情况,只需要双指针技巧,从两端向中间逼近即可:

def isPalindrome(s: str) -> bool:
    # 一左一右两个指针相向而行
    left, right = 0, len(s) - 1
    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    return True

因为回文串是对称的,所以正着读和倒着读应该是一样的,这一特点是解决回文串问题的关键

、判断回文单链表

看下力扣第 234 题「回文链表」:

这道题的关键在于,单链表无法倒着遍历,无法使用双指针技巧。

那么最简单的办法就是,把原始链表反转存入一条新的链表,然后比较这两条链表是否相同。关于如何反转链表,可以参见前文 递归翻转链表的一部分

其实,借助二叉树后序遍历的思路,不需要显式反转原始链表也可以倒序遍历链表,下面来具体聊聊。

对于二叉树的几种遍历方式,我们再熟悉不过了:

def traverse(root: TreeNode) -> None:
    # 前序遍历代码
    traverse(root.left)
    # 中序遍历代码
    traverse(root.right)
    # 后序遍历代码

在 学习数据结构的框架思维 中说过,链表兼具递归结构,树结构不过是链表的衍生。那么,链表其实也可以有前序遍历和后序遍历

def traverse(head: ListNode):
    # 前序遍历代码
    traverse(head.next)
    # 后序遍历代码

【算法思路】

这个框架有什么指导意义呢?如果我想正序打印链表中的 val 值,可以在前序遍历位置写代码;反之,如果想倒序遍历链表,就可以在后序遍历位置操作:

# 倒序打印单链表中的元素值
def traverse(head: ListNode):
    if head is None:
        return
    traverse(head.next)
    # 后序遍历代码
    print(head.val)

说到这了,其实可以稍作修改,模仿双指针实现回文判断的功能:

【python】

class Solution:
    # 从左向右移动的指针
    left = None
    # 从右向左移动的指针
    right = None

    # 记录链表是否为回文
    res = True

    def isPalindrome(self, head: ListNode) -> bool:
        self.left = head
        self.traverse(head)
        return self.res

    def traverse(self, right: ListNode):
        if right is None:
            return

        # 利用递归,走到链表尾部
        self.traverse(right.next)

        # 后序遍历位置,此时的 right 指针指向链表右侧尾部
        # 所以可以和 left 指针比较,判断是否是回文链表
        if self.left.val != right.val:
            self.res = False
        self.left = self.left.next

实际上就是把链表节点放入一个栈,然后再拿出来,这时候元素顺序就是反的,只不过我们利用的是递归函数的堆栈而已。

、优化空间复杂度

【算法思路】

1、先通过 链表双指针技巧 中的快慢指针来找到链表的中点

2、如果fast指针没有指向null,说明链表长度为奇数,slow还要再前进一步

3、从slow开始反转后面的链表,现在就可以开始比较回文串了

left, right = head, reverse(slow)

while right:
    if left.val != right.val:
        return False
    left, right = left.next, right.next

return True

至此,把上面 3 段代码合在一起就高效地解决这个问题了,其中 reverse 函数很容易实现:

【python】

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        # 寻找链表中点
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        # 对于奇数链表,slow要走到中点下一位,方便后续反转链表
        if fast:
            slow = slow.next

        left = head
        right = self.reverse(slow)
        # 反转后半段链表后,就可以顺序遍历left和right判断是否回文
        while right:
            if left.val != right.val:
                return False
            left = left.next
            right = right.next

        return True

    def reverse(self, head: ListNode) -> ListNode:
        pre = None
        cur = head
        while cur:
            next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        return pre

 【算法动态图】

算法总体的时间复杂度 O(N),空间复杂度 O(1),已经是最优的了。

我知道肯定有读者会问:这种解法虽然高效,但破坏了输入链表的原始结构,能不能避免这个瑕疵呢?

其实这个问题很好解决,关键在于得到p, q这两个指针位置:

 

 这样,只要在函数 return 之前加一段代码即可恢复原先链表顺序:

p.next = reverse(q);

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
判断一个链表是否是另一个链表的子序列,可以使用模式匹配的思想。假设我们有两个链表A和B,我们需要判断B是否是A的连续子序列。 我们可以使用两个指针,分别指向链表A和链表B的头节点。然后,我们逐个比较两个链表中的节点值是否相等。如果相等,则同时向后移动指针;如果不相等,则将链表A的指针重新指向本轮开始位置的下一个节点,并将链表B的指针重新指向B的头节点。接着,再次比较两个链表中的节点值。重复这个过程,直到链表B中的所有节点都匹配完毕。 最后,判断链表B的指针是否已经遍历到链表B的末尾。如果遍历到末尾,说明链表B是链表A的连续子序列;否则,说明链表B不是链表A的连续子序列。 以上是判断链表B是否是链表A的连续子序列的算法实现。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [判断链表是否为回文链表leetcode-Algorithms:Coding_Interviews和Leetcode](https://download.csdn.net/download/weixin_38744962/19945642)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [简单模式匹配算法的链式存储结构实现——判定一个链表是否为另一个链表的连续子序列](https://blog.csdn.net/weixin_46127065/article/details/121091631)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值