先附上题目链接234. 回文链表 - 力扣(LeetCode)
题目本身并不难,最简单的思路:将链表每个节点的值保存在集合中,遍历集合,时空复杂度都是O(n)
但是,题目要求空间复杂度为O(1),这要求我们不能使用额外空间,我的方法是反转一半的链表(没有做过反转链表的可以先看看这道206. 反转链表 - 力扣(LeetCode)),与后一半作比较,和官解的思路差不多,最后我们再和官解对比一下。
已有思路,开始写代码:
首先获得链表的长度(很简单,一次遍历链表):
int len = 0;
ListNode curr = head;
//遍历一次,找出链表长度
while(curr != null){
len ++;
curr = curr.next;
}
然后是反转前半部分链表,对此其实需要进行分类讨论 -> 链表长度奇偶性:如果为奇数,我们需要翻转除了最中间节点的前面所有节点,如果是偶数就不存在最中间的节点了,直接反转前半部分即可, 对此,我们可以封装一个方法,降低代码的冗余:
private boolean reversePartList(ListNode head, int len, boolean isOdd){ //链表头节点,需要反转的长度,链表总长度是否为奇数
ListNode currHead = head, rightListHead = null, pre = null; // 当前链表头节点, 右侧链表右节点,前驱指针
int count = 0; //记录已经翻转的长度
// 反转区间链表
while(count < len){
ListNode next = currHead.next;
currHead.next = pre;
pre = currHead;
currHead = next;
count++;
rightListHead = currHead;
}
currHead = pre;
if(isOdd) rightListHead = rightListHead.next; // 若总长为奇数,则第二个链表的头节点应在下一个节点
// 遍历比较两个链表
while(currHead != null && rightListHead != null && currHead.val == rightListHead.val){
currHead = currHead.next;
rightListHead = rightListHead.next;
}
return currHead == null && rightListHead == null;
}
时间复杂度:明显是O(n),我们只遍历了一次链表获取长度,然后遍历了一半的链表并反转
空间复杂度:满足O(1),只使用了常数个变量,但是实际上,这种O(1)的方法内存占用比O(n)的方法,也就是用集合保存节点的值还要高,也是难绷哈哈哈
附上完整代码:
public boolean isPalindrome(ListNode head) {
if(head.next == null) return true;
int len = 0;
ListNode curr = head;
//遍历一次,找出链表长度
while(curr != null){
len ++;
curr = curr.next;
}
return reversePartList(head, len / 2, len % 2 == 1);
}
最后,让我们来看看官解:
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;
}
}
它的意思是,快慢指针找出中间节点(实际上,官解比我还慢,笑死)
我认为,我的方法理解起来还是比较容易的,比较适合新手,制作不易,大家点个赞吧!