赞
踩
回文链表是指链表中的结点值正着读、反着读结果都是一样的链表。题目要求判断一个单链表是否为回文链表。
判断回文字符串使用双指针法相对容易,但是由于链表不能直接倒序遍历,所以需要另寻他法。
算法的时间和空间复杂度都是 O(N)。
由于链表不能直接倒序遍历,所以考虑生成一个原链表的反转链表。
之后遍历原链表与反转链表,对比两个链表是否相同。
相同即为回文链表,不同则不是回文链表。
生成单链表的反转链表,可以参考反转链表。
此处采取一直新的倒序遍历方法,不采用显式反转链表的方法。
下面来介绍一种神奇的链表遍历框架
举个简单例子来说明框架的使用。
如果想要正序打印链表的值,可以将 print 写在前序遍历代码的位置。
如果想要倒序打印链表的值,可以将 print 写在后序遍历代码的位置,如下图所示。
这种框架的本质是: “实际上就是把链表节点放入一个栈,然后再拿出来,这时候元素顺序就是反的,只不过我们利用的是递归函数的堆栈而已。”
下面开始动手实现上述框架。
遍历整个链表,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; } };
算法的时间复杂度是 O(N),空间复杂度是 O(1) 。递归法重新生成了一个新的链表,占用了很大的空间。使用快慢指针法可以在原有链表的基础上做判断,降低空间复杂度。
回文链表中的结点值应该是关于链表中点对称的。因此可以考虑反转链表中点后的部分,并与链表中点前的部分做比较,如果相同则说明是回文链表,如果不同则说明不是回文链表。
那么如何确定链表的中点位置呢?
使用快慢指针法找链表的中点位置。
快慢指针顾名思义有两个指针,一个为 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; } };
链表中结点个数可以为奇数,也可以为偶数。
所以判断条件应写成 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);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。