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)
思路二:
先使用快慢指针找到中间结点,如果链表长度为奇数,那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 }
复杂度分析:
时间复杂度: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 }
复杂度分析:
时间复杂度:O(n), 整体分成3部分,寻找中间结点的时间花费为O(n/2), 翻转后半段链表的时间花费也为O(n/2), 最后同时遍历左右链表的时间复杂的为O(n/2), 恢复链表的时间复杂度为O(n/2). 所以整体时间复杂度为O(n)
空间复杂度: 没有借助递归或者其他容器,所以空间复杂度为O(1)