请判断一个链表是否为回文链表。

示例 1:

  1. 输入: 1->2
  2. 输出: false

示例 2:

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

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

解题思路

image.png

回文的意思就是从两端往中间走,每一个节点都是相同的,类似于一个镜像

数组双向遍历

利用数组下标,可以直接依次双向移动索引,逐一比较遍历,当索引相同(奇数元素数量)时或者 left > right(偶数元素数量) 则表示遍历结束
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
代码中用List来存放

public boolean isPalindrome(ListNode head) {
    List<Integer> valList = new ArrayList<Integer>();

    ListNode cur = head;
    while (cur != null) {
        valList.add(cur.val);
        cur = cur.next;
    }

    int left = 0;
    int right = valList.size() - 1;
    while (true) {
        if (left == right) {
            return true;
        }
        if (left > right) {
            return true;
        }
        if (valList.get(left) != valList.get(right)) {
            return false;
        }
        left++;
        right--;
    }
}

代码优化:

public boolean isPalindrome(ListNode head) {
    List<Integer> valList = new ArrayList<Integer>();

    ListNode cur = head;
    while (cur != null) {
        valList.add(cur.val);
        cur = cur.next;
    }

    int left = 0;
    int right = valList.size() - 1;
    while (left < right) {
        if (valList.get(left) != valList.get(right)) {
            return false;
        }
        left++;
        right--;
    }
     return true;
}

复杂度分析
时间复杂度:O(n),其中 n 指的是链表的元素个数。
第一步: 遍历链表并将值复制到数组中,O(n)。
第二步:双指针判断是否为回文,执行了 O(n/2) 次的判断,即 O(n)。
总的时间复杂度:O(2n) = O(n)。
空间复杂度:O(n),其中 n 指的是链表的元素个数,使用了一个数组列表存放链表的元素值。

进阶算法:快慢指针

进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
如果需要将 O(n) 的空间复杂度变化为 O(1) ,那么就不能借用其他数据结构存储元素,只能利用指针

image.png

image.png

假设链表是下半部分的顺序,那么只需要依次比较 left 和 right 指向的结点值是否一致即可
什么是遍历终止条件?当 right 指向的结点的下一个结点是 null 时,或者 left 已经到达链表中部结点时,则遍历结束

那么算法问题转变为如下几个问题:
1.怎么反转链表后半部分结点
2.链表反转后,怎么让前半部分和后半部分同时遍历?

如果需要反转链表后半部分结点,需要先找到链表前半部分的最后一个结点

寻找链表前半部分的最后一个结点

假设链表的结点数量是偶数
image.png
image.png
image.png
假设链表的结点数量是偶数
image.png
image.png
image.png
image.png
当 fast 指针指向的结点的下一个结点或者下下一个结点是 null 时,此时 slow 指针指向的结点是前半部分最后一个结点

private ListNode endOfFrontHalf(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;
}

反转链表

反转链表可以参考 206 反转链表

private ListNode reverseList(ListNode head) {
    ListNode pre = null;
    ListNode cur = head;

    while (cur != null) {
        ListNode temp = cur.next;
        cur.next = pre;
        pre = cur;
        cur = temp;
    }

    return pre;
}

快慢指针算法

public boolean isPalindrome(ListNode head) {
    // 找到前半部分链表最后一个结点
    ListNode endOfFrontHalf = this.endOfFrontHalf(head);
    // 反转后半部分链表,并且得到后半部分的第一个结点
    ListNode startOfBehindHalf = this.reverseList(endOfFrontHalf.next);

    ListNode left = head;
    ListNode right = startOfBehindHalf;
    boolean result = true;
    while (result && right != null) {
        if (left.val != right.val) {
            result = false;
        }
        left = left.next;
        right = right.next;
    }

    // 还原链表
    endOfFrontHalf.next = this.reverseList(startOfBehindHalf);
    return result;
}