Leetcode 234. 回文链表(进阶)
1.题目描述
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2 输出: false
示例 2:
输入: 1->2->2->1 输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
2.解法一:(空间复杂度O(n))
遍历一遍链表压栈,借助栈把链表倒序,然后依次比较“原链表元素”和“新栈中元素”,如果都相等则返回true,否则返回false。
这样简单粗暴,代码的主体包含在解法二中了,这里不列出了。
另外,这种解法的时间要求能不能通过Leetcode的测试,我没有试过,因为觉得没必要试。
3.解法二:(空间复杂度O(n/2))
解题思路:使用两个指针,fast和slow指针。
(1)fast指针每次走两步,slow指针每次走一步;
(2)fast指针走到链表末尾的时候,slow指针走到链表的中间位置结点(链表长度n为偶数)或中间位置的前一个结点(链表长度n为奇数);
(1)——>(2)——>(3)——>(2)——>(1) slow fast (1)——>(2)——>(3)——>(3)——>(2) ——> (1) slow (fast) 多走1步 fast
(3)slow直接到了中间,就可以将整个链表的后半部分压栈实现逆序,依次和前半部分比较,思路同解法一。
注:就是在这里,额外的时间复杂度减少了n/2,因为只需要将链表中一半的元素压栈。
其他的细节,代码里有详细注释。
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ static const auto __ = []() { // turn off sync std::ios::sync_with_stdio(false); // untie in/out streams std::cin.tie(nullptr); return nullptr; }(); class Solution { public: bool isPalindrome(ListNode* head) { //额外空间复杂度O(n/2) /*使用两个指针,fast指针每次走两步,slow指针每次走一步;fast指针走到链表末尾的时候, slow指针走到链表的中间位置结点(链表长度n为偶数)或中间位置的前一个结点(链表长度n为奇数) */ //判空 if(head == NULL) return true; //单节点链表 if(head->next == NULL) return true; ListNode* fast = head; ListNode* slow = head;//指向第一个结点 //fast指针指向末尾结点,slow指针指向中间位置结点或中间位置的前一个结点 ////注意:这里的结束判断主要看fast!!! while(fast->next != NULL && fast->next->next != NULL ) { fast = fast->next->next; slow = slow->next; } //链表长度为偶数,fast指针最后多走一步到链表末尾 if(fast->next) fast = fast->next; stack<int> s; //将链表后半部分元素压栈,通过栈来实现逆序 while(slow->next) { s.push(slow->next->val); slow = slow->next; } //依次比较前半部分元素和逆序的后半部分元素 while(!s.empty()) { if(s.top() != head->val) return false; //前、后一起往后移动 s.pop(); head = head->next; } return true; } };
4.解法三:(进阶:空间复杂度O(1))
解题思路:解法三和解法二的区别在于,最后不使用栈来倒序链表后半部分的元素,而是选择直接本地操作(额外空间复杂度为O(1)),在原链表上将后半部分元素倒置(反转),比较完后得出结果后,再 还原链表,返回结果。
代码中有详细注释。
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ static const auto __ = []() { // turn off sync std::ios::sync_with_stdio(false); // untie in/out streams std::cin.tie(nullptr); return nullptr; }(); class Solution { public: bool isPalindrome(ListNode* head) { //额外空间复杂度O(1) //判空 if(head == NULL) return true; //单节点链表 if(head->next == NULL) return true; //双节点链表 if(head->next->next == NULL) if(head->val == head->next->val) return true; else return false; ListNode* fast = head; ListNode* slow = head;//指向第一个结点 //fast指针指向末尾结点,slow指针指向中间位置结点或中间位置的前一个结点 ////注意:这里的结束判断主要看fast!!! while(fast->next != NULL && fast->next->next != NULL ) { fast = fast->next->next; slow = slow->next; } //链表长度为偶数,fast指针最后多走一步到链表末尾 if(fast->next) fast = fast->next; //-----区别在这里,元素不压栈,直接将链表后半部分元素逆序,比较完后得出结果后,再还原链表,返回结果--------// //---------------链表的后半部分元素“倒序”-------------------// ListNode* p = slow->next; ListNode* q = NULL; ListNode* cur = NULL; slow->next = NULL; while(p) { cur = p->next; p->next = q; q = p; p = cur; } //依次比较 前半部分元素 和 逆序的后半部分元素 while(1) { if(fast->val != head->val) { //链表复原 ListNode* m = q->next; ListNode* n = NULL; ListNode* cur2 = NULL; q->next = NULL; while(m){ cur2 = m->next; m->next = n; n = m; m = cur2; } slow->next = n; return false; } //前、后一起往后移动 fast = fast->next; head = head->next;
//--------在这里判断结束,是调试的结果----//
//针对这种情况:(3)——>(1)——>(2)——>(3),后半部分反转之后的链表是(3)——>(1)——>(3)——>(2)
// 如果使用while(head->next)作为结束,会少比较一次,也就是最后(1)和(2)不会比较到,从而出错 if(fast == NULL) break; } //链表复原 ListNode* m = q->next; ListNode* n = NULL; ListNode* cur2 = NULL; q->next = NULL; while(m){ cur2 = m->next; m->next = n; n = m; m = cur2; } slow->next = n; return true; } };
参考资料:
1.https://blog.csdn.net/blioo/article/details/62050967 单向链表反转(倒置)问题