LeetCode234 &《程序员面试金典》面试题 02.06. 回文链表

这篇博客详细介绍了如何解决LeetCode234问题——回文链表。作者通过三种解题方法:反转并比较、迭代法和递归法,阐述了解题思路和技巧。在迭代法中,使用栈辅助判断,而在递归法中,利用链表特性进行前后半部分的比较。博客强调了在实际操作中要考虑是否能修改原链表,并探讨了优化空间复杂度的可能性。
摘要由CSDN通过智能技术生成

LeetCode234 &《程序员面试金典》面试题 02.06. 回文链表

题目

在这里插入图片描述

解题

要解决这个问题,可以将回文 (palindrome) 定义为 0 -> 1 -> 2 -> 1 -> 0。显然,若链表是回文,不管正着看还是反着看,都是一样的。

解题一:反转并比较

做题目的时候可能是脑子出去神游了,竟然特开心地在反转链表时没有new node,直接拿的原链表节点,结果把原链表给改了,比较的时候就比较了个寂寞。以后解题之前应该先判断一下原链表是否可以被修改,如果可以便不用额外的空间,如果不可以那要确保操作不能改变原链表。

官解喜欢写子函数来完成一个一个小操作,我自己常把所有代码敲在一个函数里,代码少的话是没什么问题,但是平时开发中不易读和进行维护。所以,这一点要向官解学习!

在比较原始链表和反转链表时,其实只需比较链表的前半部分。若原始链表和反转链表的前半部分相同,那么,两者的后半部分肯定相同。即长度为5的链表只需要比较前2个节点,长度为4的链表也只用比较前2个节点。

// javascript
var ListNodeWithLen = function(node, len) {
    this.node = node;
    this.len = len;
};

// 反转链表
var reverseList = function(head) {
    let rev = new ListNodeWithLen(null, 0);
    while (head !== null) {
        rev.len++;
        let newNode = new ListNode(head.val);
        newNode.next = rev.node;
        rev.node = newNode;
        head = head.next;
    }
    return rev;
};

// 比较两个链表是否相同
var isEqual = function(l1, l2, length) {
	let count = length >> 1;
    for (let i = 0; i < count; i++) { // [] 进不了 for loop
        if (l1.val !== l2.val) return false;
        l1 = l1.next;
        l2 = l2.next;
    }
    return true;
};

var isPalindrome = function(head) {
    let rev = reverseList(head);
    return isEqual(head, rev.node, rev.len);
};

时间复杂度和空间复杂度都是 O ( N ) O(N) O(N)。从时间复杂度上来看,必须遍历链表的每一个节点后才可以判断是否是回文,所以应该尝试从空间角度去优化,题目也给了提示,问能不能用 O ( 1 ) O(1) O(1)的空间复杂度来实现。

解题二:迭代法

在这里插入图片描述
用栈来实现,栈是后进先出,可以想象一下堆碟子,后堆上去的碟子是可以先被拿出来,可以用一个数组去实现。

为了找到中间节点,用了快慢runner,慢runner走一步,快runner走两边,当快runner到尾部时,慢runner正好在中间位置。

// javascript
var isPalindrome = function(head) {
    let fast = head, slow = head;
    let arr = [];
    // 将链表前半部分元素插入到栈中
    // 当快指针 (2 倍速移动),移动到链表尾部,慢指针到达中点
    while (fast !== null && fast.next !== null) {
        arr.push(slow.val);
        slow = slow.next;
        fast = fast.next.next;
    }
    // 因为有奇数个节点,所以跳过中间节点
    if (fast !== null) slow = slow.next;
    while (slow !== null) {
    	// 如果值不同,则不是回文
        if (slow.val !== arr.pop()) return false;
        slow = slow.next;
    }
    return true;
};

但是这里面用栈来存储前半段链表节点值,所以还是用了额外的空间,避免使用 O ( N ) O(N) O(N)额外空间的方法就是改变输入
在这里插入图片描述

// javascript
const reverseList = (head) => {
    let prev = null;
    let curr = head;
    while (curr !== null) {
        let nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
};

const endOfFirstHalf = (head) => {
    let fast = head, slow = head;
    while (fast.next !== null && fast.next.next !== null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
};

var isPalindrome = function(head) {
    if (head === null) return true;

    // 找到前半部分链表的尾节点并反转后半部分链表
    const firstHalfEnd = endOfFirstHalf(head);
    const secondHalfStart = reverseList(firstHalfEnd.next);

    // 判断是否回文
    let p1 = head;
    let p2 = secondHalfStart;
    let res = true;
    while (res && p2 !== null) {
        if (p1.val !== p2.val) {
            res = false;
        }
        p1 = p1.next;
        p2 = p2.next;
    }

    // 还原链表并返回结果
    firstHalfEnd.next = reverseList(secondHalfStart);
    return res;
};

解法三:递归法

LeetCode官网上的解题思路会比书上的解法更容易理解。如果是数组,可以用头尾两个指针去遍历,链表只能从前往后遍历,不能从后往前遍历(不知道前一个节点),所以用递归的方法来实现尾指针的向前遍历。
在这里插入图片描述

// javascript
let frontPointer;

const reversivelyCheck = (currentNode) => {
    if (currentNode === null) return true;
    let cmp = reversivelyCheck(currentNode.next);
    if (cmp === false || currentNode.val !== frontPointer.val) return false;
    frontPointer = frontPointer.next;
    return true;
};

var isPalindrome = function(head) {
    frontPointer = head;
    return reversivelyCheck(head);
};

如果不将 frontPointer 设置为全局变量,可以用下面的写法进行传递:

//javascript
var NodeWithCmp = function(node, cmp) {
    this.node = node;
    this.cmp = cmp;
};

var isEqual = function(head, curNode) {
    if (curNode === null) return new NodeWithCmp(head, true);
    let res = isEqual(head, curNode.next);
    if (res.cmp === false || res.node.val !== curNode.val) return new NodeWithCmp(null, false);
    res.node = res.node.next;
    return res;
};

var isPalindrome = function(head) {
    let res = isEqual(head, head);
    return res.cmp;
};

在这里插入图片描述
书上花了比较长的篇幅来描述推演的思路,该方法预先计算链表的长度,只比较了链表的前半部分,而上面的解法其实进行了重复比较。递归是我的薄弱环节,但是在链表的遍历中它确实有天然的优势。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

// javascript
var NodeWithCmp = function(node, cmp) {
    this.node = node;
    this.cmp = cmp;
};

var getLength = function (l) {
    var length = 0;
    while (l !== null) {
        length++;
        l = l.next;
    }
    return length;
};

var isPalindromeRecurse = function(head, length) {
    if (length === 0) { // 偶数个节点
        return new NodeWithCmp(head, true);
    }
    if (length === 1) { // 奇数个节点
        return new NodeWithCmp(head.next, true);
    }
    // 在子链表上递归
    let res = isPalindromeRecurse(head.next, length - 2);
    if (res.cmp === false) { // 如果递归调用返回非回文,则向上传递失败信息
        return new NodeWithCmp(null, false);;
    }
    res.cmp = (res.node.val === head.val); // 检查与另一侧的节点值是否匹配
    res.node = res.node.next;
    return res; // 返回对应的节点
};

var isPalindrome = function(head) {
    let length = getLength(head);
    let res = isPalindromeRecurse(head, length);
    return res.cmp;
};

LeetCode官方解答

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值