力扣 234. 回文链表

234. 回文链表

请判断一个链表是否为回文链表。
示例 1:

输入: 1->2
输出: false

示例 2:

输入: 1->2->2->1
输出: true

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

思路一:

借助一个列表,先遍历链表存下所有结点,然后第二次遍历链表比较列表中的结点

 1 class Solution {
 2     
 3     public boolean isPalindrome(ListNode head) {
 4         ArrayList<ListNode> list = new ArrayList<>();
 5         for(ListNode node = head; node != null; node = node.next){
 6             list.add(node);
 7         }
 8         int i = list.size()-1;
 9         for(ListNode node = head; node != null && i >= 0; node = node.next, i--){
10             if(node.val != list.get(i).val){
11                 return false;
12             }
13         }
14         return true;
15     }
16 }

力扣测试时间为2ms, 空间为43.2MB

复杂度分析:

时间复杂度:遍历了两次链表,所以时间复杂度为O(n)

空间复杂度:O(n)

思路二:

思路参考:https://leetcode-cn.com/problems/palindrome-linked-list/solution/hui-wen-lian-biao-by-leetcode/223310

先使用快慢指针找到中间结点,如果链表长度为奇数,那slow所指结点刚好是中间结点,如果长度为偶数,那slow所指结点刚好是等分后的链表的后半段的第一个结点,

随后翻转slow所指的后半段链表,完成翻转后pre是后半段链表的头结点

同时遍历后半段链表和前半段链表,判断是否是回文链表

 1 class Solution {
 2     public boolean isPalindrome(ListNode head) {
 3         if(head == null || head.next == null){
 4             return true;
 5         }
 6         // 使用快慢指针找到中间结点
 7         ListNode fast = head, slow = head;
 8         while(fast != null && fast.next != null){
 9             fast = fast.next.next;
10             slow = slow.next;
11         }
12 
13         // 如果链表长度为奇数,那slow所指结点刚好是中间结点,如果长度为偶数,那slow所指结点刚好是等分后的链表的后半段的第一个结点
14         ListNode left = head;   // 前半段链表
15         ListNode cur = slow, pre = null, next = null;   // 最后pre所指的链表就是后半段链表
16         // 翻转slow所指的后半段链表
17         while(cur != null){
18             next = cur.next;
19             cur.next = pre;
20             pre = cur;
21             cur = next;
22         }
23 
24         // 同时遍历后半段链表和前半段链表,判断是否是回文链表
25         ListNode right = pre;
26         while(left != null && right != null){    // 后半段比前半段长度大于等于0
27             if(left.val != right.val){
28                 return false;
29             }
30             left = left.next;
31             right = right.next;
32         }
33         return true;
34     }
35 }
leetcode 执行用时:1 ms > 99.86%, 内存消耗:40.7 MB > 99.79%

复杂度分析:

时间复杂度:O(n), 整体分成3部分,寻找中间结点的时间花费为O(n/2), 翻转后半段链表的时间花费也为O(n/2), 最后同时遍历左右链表的时间复杂的为O(n/2), 所以整体时间复杂度为O(n)

空间复杂度:  没有借助递归或者其他容器,所以空间复杂度为O(1)

改进版

因为我们对原链表的后半段进行了翻转,所以修改了原链表,所以其实得到结果后应该把链表回复成原样。

回复原样的过程其实也很简单,仔细观察,我们找到中间结点后,并没有断开前半段链表对后半段链表的连接,所以其实翻转后的链表是这样的,以1 -> 2->3->2->1为例,翻转后后的链表为1-2->3<-2<-1, 翻转后,pre指针指向了后半段链表的头结点,slow指针指向了中间结点,所以恢复链表,只需把pre 到 slow之间的结点再翻转一次即可。如果链表为1->2->3->3->2->1, 则翻转后的链表为 1->2->3->3<-2<-1. 此时slow指向了第二个3, pre指向了最后一个1,翻转pre到slow之间的结点即完成了链表的恢复工作。

根据上面标红的右半段链表,可以看出做半段链表其实总是大于等于右半段链表的长度的,所以我们在判断是否回文的时候,可以使用遍历右半段端链表的指针为空来作为结束循环的条件。

 1 class Solution {
 2     public boolean isPalindrome(ListNode head) {
 3         if(head == null || head.next == null){
 4             return true;
 5         }
 6         // 使用快慢指针找到中间结点
 7         ListNode fast = head, slow = head;
 8         while(fast != null && fast.next != null){
 9             fast = fast.next.next;
10             slow = slow.next;
11         }
12 
13         // 如果链表长度为奇数,那slow所指结点刚好是中间结点,如果长度为偶数,那slow所指结点刚好是等分后的链表的后半段的第一个结点
14         ListNode left = head;   // 前半段链表
15         ListNode cur = slow, pre = null, next = null;   // 最后pre所指的链表就是后半段链表
16         // 翻转slow所指的后半段链表
17         while(cur != null){
18             next = cur.next;
19             cur.next = pre;
20             pre = cur;
21             cur = next;
22         }
23 
24         // 同时遍历后半段链表和前半段链表,判断是否是回文链表
25         ListNode right = pre;
26          // 根据上面的推论,可知右半段链表其实长度小于等于做半段链表
27         while(right != null){   
28             if(left.val != right.val){
29                 return false;
30             }
31             left = left.next;
32             right = right.next;
33         }
34 
35         // 恢复链表,将pre到slow之间的结点再翻转一次即可
36         cur = pre;
37         pre = null;
38         next = null; 
39         while(cur != slow){
40             next = cur.next;
41             cur.next = pre;
42             pre = cur;
43             cur = next;
44         }
45         return true;
46     }
47 }
leetcode 执行用时:1 ms > 99.86%, 内存消耗:40.7 MB > 99.79%

复杂度分析:

时间复杂度:O(n), 整体分成3部分,寻找中间结点的时间花费为O(n/2), 翻转后半段链表的时间花费也为O(n/2), 最后同时遍历左右链表的时间复杂的为O(n/2), 恢复链表的时间复杂度为O(n/2). 所以整体时间复杂度为O(n)

空间复杂度:  没有借助递归或者其他容器,所以空间复杂度为O(1)

posted @ 2020-06-21 19:47  Lucky小黄人^_^  阅读(225)  评论(0编辑  收藏  举报