【链表】力扣234:回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

示例1:

image
输入:head = [1,2,2,1]
输出:true

示例2:

输入:head = [1]
输出:true

数组储存再反转比较

在 Python 中,很容易构造一个列表的反向副本,也很容易比较两个列表,所以没有必要用双指针法。

需要注意的是,比较的是结点值的大小,而不是结点本身。正确的比较方式是node1.val == node2.val,而 node1 == node2 是错误的。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        if not head: # 特殊情况先行判断
            return False
        vals = []
        while head:
            vals.append(haed.val)
            head = head.next
        return vals == vals[::-1]
        '''
        1. 判断方式用双指针法
        j = len(vals) - 1
        for i in range(len(vals)):
            if vals[i] != vals[j]: # 比较的是结点的值的大小
                return False
            j -= 1
        return True

        2. 判断方式用单指针法
        n = len(vals)
        for i in range(n // 2):
            if vals[i] != vals[n -1 - i]:
                return False
        return True
        '''

时间复杂度:O(n),其中 n 指的是链表的元素个数。

第一步: 遍历链表并将值复制到数组中,O(n)。

第二步:双指针判断是否为回文,执行了 O(n/2) 次的判断,即 O(n)。

总的时间复杂度:O(2n) = O(n)。

空间复杂度:O(n)。使用了一个数组列表存放链表的元素值。

栈储存再pop比较

遇到对称有关的问题应该首先想用stack能不能解决,如果链表元素个数为偶数,先把一半的元素放入stack,然后每次用栈顶元素和下一个节点比较,如果一直比较成功就是true,否则false。如果是奇数个,先将一半加一个元素入栈,然后先出栈一次在做上述比较。

参考:https://leetcode.cn/problems/palindrome-linked-list/solution/hui-wen-lian-biao-de-san-chong-fang-fa-by-coldme-2/

整体堆栈

  • 遍历链表,把每个节点都 push 到 stack

  • 再次遍历链表,同时栈内结点依次 pop,二者进行比较

时间复杂度 O(N),空间复杂度 O(N)

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        stack = []

        # step1: push
        cur = head
        while cur:
            stack.append(cur)
            cur = cur.next

        # step2: pop and compare
        node1 = head
        while stack:
            node2 = stack.pop()
            if node1.val != node2.val:
                return False
            node1 = node1.next
        return True

作者:coldme-2
链接:https://leetcode.cn/problems/palindrome-linked-list/solution/hui-wen-lian-biao-de-san-chong-fang-fa-by-coldme-2/

一半堆栈

  • 利用快慢双指针 slow 和 fast 来遍历链表,以找到链表中间的位置

  • 链表的前一半入栈,后一半与出栈的结点依次比较

需要考虑链表长度是奇数还是偶数,这取决于终止时 fast 为空还是 fast.next 为空。

  • head 位于第一个结点

  • 对于奇数,如1->2->3->2->1,此时快指针 fast 会停在最后的 1 处(fast 不为空,而 fast.next 为空),慢指针 slow 停在中间的 3 处,这时需要对 slow.next 的链表进行翻转

  • 对于偶数,结束循坏时 fast 和 fast.next 均为空,慢指针 slow 停在链表后半部分的第一个位置,则不需要在 pop比较 之前继续移动 slow

时间复杂度 O(N),空间复杂度 O(N),但比整体堆栈节省一半的空间。

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        stack = []
        slow, fast = head, head # 快慢指针均指向头结点

        # step1: push
        while fast and fast.next:
            stack.append(slow) # 链表前一半依次入栈
            slow = slow.next
            fast = fast.next.next
        if fast:
            slow = slow.next

        # step2: pop and compare
        while stack:
            cur = stack.pop()
            if cur.val != slow.val:
                return False
            slow = slow.next
        return True

作者:coldme-2
链接:https://leetcode.cn/problems/palindrome-linked-list/solution/hui-wen-lian-biao-de-san-chong-fang-fa-by-coldme-2/

递归

使用递归反向迭代节点,同时使用递归函数外的变量向前迭代,就可以判断链表是否为回文。

  • 建立 curr 指针先到尾节点,由于递归的特性再从后往前进行比较。建立 递归函数外的指针 prev。

  • 若 curr.val != prev.val 则返回 false。否则,prev 向前移动,并返回 true。

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        self.prev = head

        def recursively_check(curr = head):
            if curr:
                if not recursively_check(curr.next):
                    return False
                if self.prev.val != curr.val:
                    return False
                self.prev = self.prev.next
            return True

        return recursively_check()

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/palindrome-linked-list/solution/hui-wen-lian-biao-by-leetcode-solution/

时间复杂度:O(n),其中 n 指的是链表的大小。

空间复杂度:O(n)。

要理解计算机如何运行递归函数,在一个函数中调用一个函数时,计算机需要在进入被调用函数之前跟踪它在当前函数中的位置(以及任何局部变量的值),通过运行时存放在堆栈中来实现(堆栈帧)。在堆栈中存放好了数据后就可以进入被调用的函数。在完成被调用函数之后,会弹出堆栈顶部元素,以恢复在进行函数调用之前所在的函数。在进行回文检查之前,递归函数将在堆栈中创建 n 个堆栈帧,计算机会逐个弹出进行处理。所以在使用递归时空间复杂度要考虑堆栈的使用情况。

在许多语言中,堆栈帧的开销很大(如 Python),并且最大的运行时堆栈深度为 1000(可以增加,但是有可能导致底层解释程序内存出错)。为每个结点创建堆栈帧极大的限制了算法能够处理的最大链表大小。

快慢指针(the best)

避免使用 O(n) 额外空间的方法就是改变输入。

可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分的值进行比较。比较完成后应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。

  • 找到前半部分链表的尾节点

    • 设置快慢指针。每次快指针增加两个,慢指针增加一个。这样当快指针结尾时,慢指针指向了链表的中间
  • 反转后半部分链表

    • 用慢指针逆序链表的后半部分,利用Python交换的特性,不需要额外的temp

不懂就去看力扣206题:https://www.cnblogs.com/Jojo-L/p/16453891.html

  • 判断是否回文

    • 一个从头开始,一个从中间开始,判断两结点值是否相同。当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点
  • 恢复链表,即再反转一次

  • 返回结果

该方法虽然可以将空间复杂度降到 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 isPalindrome(self, head: ListNode) -> bool:
        if not head:
            return False
        slow, fast, pre = head, head, None

        # 找到前半部分链表的尾节点
        while fast:
            slow = slow.next
            fast = fast.next.next if fast.next else fast.next # 避免判断链表长度奇偶。循环结束时指针 slow 在链表后半部分第一个,fast 为空

        # 反转后半部分链表
        while slow:
            slow.next, slow, pre = pre, slow.next, slow

        # 判断是否回文
        while head and pre:
            if head.val != pre.val:
                return False
            head, pre = head.next, pre.next

        return True

时间复杂度:O(n),其中 n 指的是链表的大小。

空间复杂度:O(1)。只修改原本链表中结点的指向,而在堆栈上的堆栈帧不超过 O(1)。

前半部分比较

class Solution:
    def isPalindrome(self, head: ListNode) -> bool:

        # reverse front link when finding mid node
        slow = None
        fast = head
        back = head
        while fast and fast.next:
            fast = fast.next.next
            temp = back.next
            back.next = slow
            slow = back
            back = temp

        # modify back if odd link
        if fast:
            back = back.next

        # compare and return answer
        while back and slow.val == back.val:
            slow = slow.next
            back = back.next
        return False if back else True

作者:thuliangtian
链接:https://leetcode.cn/problems/palindrome-linked-list/solution/by-thuliangtian-h8zd/

恢复了链表版本 前半部分比较

比较的同时顺便反转前半部分

@Java

 public boolean isPalindrome(ListNode head) {
        if(head == null || head.next == null) {
            return true;
        }

        ListNode fast = head;
        ListNode slow = head;
        ListNode prev = null;

        // 快慢指针,并同时反转链表前半部分
        while (fast != null && fast.next != null) {
            fast = fast.next.next;

            // 反转
            ListNode nextNode = slow.next;
            slow.next = prev;
            prev = slow;
            slow = nextNode;
        }
        ListNode prepre = slow;
        if (fast != null) {
            slow = slow.next;
        }

        // 比较值并反转还原前半部分
        boolean isPalindrome = true;
        while (prev != null) {
            if (slow.val != prev.val) {
                isPalindrome = false;
            }
            slow = slow.next;

            // 前半部分再次反转
            ListNode nextNode = prev.next;
            prev.next = prepre;
            prepre = prev;
            prev = nextNode;
        }
        return isPalindrome;
    }

@ 蓝黑R9(LeetCode)
posted @   Vonos  阅读(72)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示