一、题目描述
这是 LeetCode 上的第二百三十四题:回文链表,难度为 简单。
Tag:「链表」、「双指针」
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
示例 1:
1 2
| 输入:head = [1,2,2,1] 输出:true
|
示例 2:
1 2
| 输入:head = [1,2] 输出:false
|
提示:
1、链表中节点数目在范围[1, 10^5] 内
2、0 <= Node.val <= 9
二、解题思路
思路:
我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。
该方法虽然可以将空间复杂度降到 O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。
整个流程可以分为以下五个步骤:
1、找到前半部分链表的尾节点。
2、反转后半部分链表。
3、判断是否回文。
4、恢复链表。
5、返回结果。
执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。
我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。
若链表有奇数个节点,则中间的节点应该看作是前半部分。
步骤二可以使用反转链表来反转链表的后半部分。
步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| class Solution { public boolean isPalindrome(ListNode head) { if (head == null) { return true; }
ListNode firstHalfEnd = endOfFirstHalf(head); ListNode secondHalfStart = reverseList(firstHalfEnd.next);
ListNode p1 = head; ListNode p2 = secondHalfStart; boolean result = true; while (result && p2 != null) { if (p1.val != p2.val) { result = false; } p1 = p1.next; p2 = p2.next; }
firstHalfEnd.next = reverseList(secondHalfStart); return result; }
private ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode nextTemp = curr.next; curr.next = prev; prev = curr; curr = nextTemp; } return prev; }
private ListNode endOfFirstHalf(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast.next != null && fast.next.next != null) { fast = fast.next.next; slow = slow.next; } return slow; } }
|
复杂度分析:
1、时间复杂度:O(n),其中 n 指的是链表的大小。
2、空间复杂度:O(1)。我们只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过 O(1)。
三、总结
本道算法题难度为简单,如果对链表不熟悉,那实现起来会相当困难,因此我们要先对链表有足够的熟悉,掌握链表一些常见基础的算法如:反转链表,快慢指针。然后注意细节,每一步思路都要很清晰,方能解决此需求。
好了,本篇文章到这里就结束了,感谢你的阅读🤝