LeetCode 之 回文链表

回文链表

  • 1. 递归法
    • 1.1 思路
    • 1.2 代码实现
  • 2. 快慢指针法
    • 2.1 思路
    • 2.2 代码实现
    • 2.3 实现细节
  • 参考文献

回文链表是指链表中的结点值正着读、反着读结果都是一样的链表。题目要求判断一个单链表是否为回文链表。
判断回文字符串使用双指针法相对容易,但是由于链表不能直接倒序遍历,所以需要另寻他法。

LeetCode 之 回文链表_第1张图片

1. 递归法

算法的时间和空间复杂度都是 O(N)。
LeetCode 之 回文链表_第2张图片

1.1 思路

由于链表不能直接倒序遍历,所以考虑生成一个原链表的反转链表。
之后遍历原链表与反转链表,对比两个链表是否相同。
相同即为回文链表,不同则不是回文链表。

1.2 代码实现

生成单链表的反转链表,可以参考反转链表。
此处采取一直新的倒序遍历方法,不采用显式反转链表的方法。

下面来介绍一种神奇的链表遍历框架
LeetCode 之 回文链表_第3张图片
举个简单例子来说明框架的使用。
如果想要正序打印链表的值,可以将 print 写在前序遍历代码的位置。
如果想要倒序打印链表的值,可以将 print 写在后序遍历代码的位置,如下图所示。

LeetCode 之 回文链表_第4张图片
这种框架的本质是: “实际上就是把链表节点放入一个栈,然后再拿出来,这时候元素顺序就是反的,只不过我们利用的是递归函数的堆栈而已。”

下面开始动手实现上述框架。
遍历整个链表,left 为链表的左指针,初始值为head,right为链表的右指针,reverse 最先弹出的 right 是链表的最后一个结点。之后,left 不断向右移动,reverse 函数 return 是否为回文链表的布尔值。

class Solution {
     
public:
    ListNode* left = NULL;
    bool isPalindrome(ListNode* head) {
     
        left = head;
        return reverse(head);
    }
    bool reverse(ListNode* right)
    {
     
    	//基础条件
    	//单结点链表为回文链表
        if(right == NULL)
            return true;
        bool res = reverse(right->next);
        //后序遍历代码
        res = res && (left->val == right->val);
        left = left->next;
        return res;

    }
};

2. 快慢指针法

算法的时间复杂度是 O(N),空间复杂度是 O(1) 。递归法重新生成了一个新的链表,占用了很大的空间。使用快慢指针法可以在原有链表的基础上做判断,降低空间复杂度

LeetCode 之 回文链表_第5张图片

2.1 思路

回文链表中的结点值应该是关于链表中点对称的。因此可以考虑反转链表中点后的部分,并与链表中点前的部分做比较,如果相同则说明是回文链表,如果不同则说明不是回文链表。
那么如何确定链表的中点位置呢?

2.2 代码实现

使用快慢指针法找链表的中点位置。
快慢指针顾名思义有两个指针,一个为 fast 指针,另一个为 slow 指针。
快慢指针法是双指针法的一种,两个指针的速度不同。要想找到中点,可以令快指针速度是慢指针速度的二倍

slow = slow->next;
fast = fast->next->next;

这样当 fast 指针到达链表末尾时,slow 指针就到达了链表中点。

class Solution {
     
public:
    bool isPalindrome(ListNode* head) {
     
        ListNode* slow = head;
        ListNode* fast = head;
        while(fast != nullptr && fast->next != nullptr)
        {
     
            slow = slow->next;
            fast = fast->next->next;

        }
        if(fast != nullptr)
            slow = slow->next;
        
        ListNode* left = head;
        ListNode* right = reverse(slow);
        while(right != nullptr)
        {
     
            if(left->val != right->val)
                return false;
            left = left->next;
            right = right->next;

        }
        return true;
    }

private:
    ListNode* reverse(ListNode* head)
    {
     
        ListNode* pre = nullptr;
        ListNode* cur = head;

        while(cur != nullptr)
        {
     
            ListNode* nxt = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }
};

2.3 实现细节

链表中结点个数可以为奇数,也可以为偶数。
所以判断条件应写成 while(fast != nullptr && fast->next != nullptr)

确切的说,找到链表的中点不是目的。我们需要找到的是,链表后半部分的头结点。
因此当链表长度为奇数时,slow 需要后移一步,作为待反转链表部分的头结点。

if(fast != nullptr)
    slow = slow->next;

反转后链表如下图所示,left 和 right 分别为链表的最左结点和最右结点。
如果 while(right != nullptr) 就一直比较 left->val 和 right->val ,直至返回 true 或 false。

这里还有一个问题需要注意:
就是上述方法破坏了链表的原有结构,那么有什么办法可以解决这个问题呢?

记录 p 、q 两个位置,在函数 return 前,将 p 和 q 连接起来,就可以恢复链表的原有结构。

p.next = reverse(q);

参考文献

  1. labuladong的算法小抄
  2. 一种更优的快慢指针法

你可能感兴趣的:(数据结构,链表,leetcode,数据结构)