【原题】https://leetcode-cn.com/problems/palindrome-linked-list/description/
题目描述
Given a singly linked list, determine if it is a palindrome.
Example 1:
Input: 1->2
Output: false
Example 2:
Input: 1->2->2->1
Output: true
Follow up:
Could you do it in O(n) time and O(1) space?
分析:
这道题有两个问题:
1、链表的长度。
2、保证时间复杂度为O(n),空间复杂度为O(1)。
第一个问题,题目所给的是一个链表,与给定一个数字相比,最大的不同在于我们不知道链表的长度,就无法简单地得到链表中间的节点。
我们可以设定一个慢指针和一个快指针,慢指针每次都一步,快指针每次走两步。这样一来,当快指针走到最后的时候,慢指针就刚好处于中间位置。
代码如下:
ListNode slow = head;
ListNode fast = head;
// 链表长度为奇数,slow正好在链表中间位置
// 链表长度为偶数,slow正好在链表中间位置偏左一位
// 1->2->2->1
// ^
// slow
// 1->2->3->2->1
// ^
// slow
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
第二个问题,如何保证时间复杂度为O(n),空间复杂度为O(1)。满足时间复杂度比较容易,遍历一遍链表即可。先不考虑空间复杂度,我们看看有哪些可行的解决方案:
1、数组,遍历链表前半段的时候,将遍历得到的元素存入一个数组中,然后接着遍历后半段,与数组中的元素比较即可。需要n/2的数组空间,空间复杂度为O(n)。
2、栈,由回文很容易想到的数据结构就是栈,先按遍历顺序压入栈,直至中间位置,然后依次出栈与遍历元素比较即可。需要n/2的栈空间,空间复杂度为O(n)。
3、反转链表,即原地将链表反转,不需要额外的储存空间。具体思路是,先将链表后半段原地反转,然后与前半段比较即可。不需要额外的空间,空间复杂度为O(1)。
具体反转链表的方法,见 每日一恋 LeetCode 206. Reverse Linked List (反转链表) 题。
下面贴出具体代码:
public boolean isPalindrome(ListNode head) {
ListNode slow = head; // 慢指针,一次走一步
ListNode fast = head; // 快指针,一次走两步
// 链表长度为 0 或 1 的情况,属于回文数
if (fast == null || fast.next == null)
return true;
// 链表长度为奇数,slow正好在链表中间位置
// 链表长度为偶数,slow正好在链表中间位置偏左一位
// 1->2->2->1
// ^
// slow
// 1->2->3->2->1
// ^
// slow
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 对链表后半段进行反转 LeetCode 206
ListNode curNode = slow.next; // 后半段链表的第一个节点
ListNode newHead = null;
while (curNode != null) {
ListNode nextNode = curNode.next;
curNode.next = newHead;
newHead = curNode;
curNode = nextNode;
}
slow = head;
fast = newHead; // 结束后newHead为后半段新的头结点,位于原链表的最后一个节点
// 只考虑后半段是否结束
// 若链表长度为奇数,前半段会比后半段多出一个节点,这个节点对判断回文没有影响
// 若为偶数,则前后半段数量相同
while (fast != null) {
if (fast.val != slow.val)
return false;
fast = fast.next;
slow = slow.next;
}
return true;
}