Bootstrap

LC234.回文链表(hot100)

先附上题目链接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;
    }
}

它的意思是,快慢指针找出中间节点(实际上,官解比我还慢,笑死)

我认为,我的方法理解起来还是比较容易的,比较适合新手,制作不易,大家点个赞吧!

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;