每日一题之回文链表

题目:

image-20240718215827871

链接:回文链表

题解:

方法一:将值复制到数组中后用双指针法(头尾指针)

遍历链表,将链表每一项放入数组中。接着设置两个指针index1,index2分别指向数组头部尾部,接着index1向移动一步,index2向移动一步,每移动一步进行一次比较,循环进行这个步骤即可。结束的条件是循环中找到一组不同项结束说明不是回文链表,或者循环结束说明是回文链表

var isPalindrome = function(head) {
    if(!head.next){
        return true
    }
    let arr =[]
    let p = head
    //遍历链表,将链表每一项放入数组中
    while(p){
        arr.push(p)
        p=p.next
    }
​
    //遍历数组,进行对比
    let index1 = 0
    let index2 = arr.length -1
    // 循环结束的条件是:
    //链表长度为偶数时:index1 == index2即可退出 
    //链表长度为偶数时:index1 >index2即可退出
    while(index1 < index2 ){
        // 找到一组不同项结束说明不是回文链表,返回false
        if(arr[index1].val != arr[index2].val){
            return false
        }
        index1++;
        index2--;
    }
    
    // 循环结束说明是回文链表
    return true
​
};

复杂度分析:

时间复杂度:O(n),空间复杂度:O(n)

方法二:递归

思路其实和第一种方式差不多,也是头尾移动进行比较。链表递归有个特点,那就是递归处理节点的顺序是相反的,即是从尾部向头部进行处理的,每一次递归,当前的节点currentNode为上一个节点向前移动一位的节点,而我们又在函数外设置一个frontPointer指针指向头部,每递归一次frontPointer指针就向后移动一位,这样也能实现类似第一种方法的头尾依次进行比较。

var isPalindrome = function(head) {
    // 递归
    let recursivelyCheck  = (currentNode)=>{
        if(currentNode != null){
        
            if(!recursivelyCheck(currentNode.next)){
                return false
            }
            if (currentNode.val !== frontPointer.val) {
                return false;
            }
            frontPointer = frontPointer.next
        }
​
        return true
    }
​
    let frontPointer = head
    return recursivelyCheck(head)
​
};

复杂度分析:

时间复杂度:O(n),空间复杂度:O(n)

方法三:快慢指针

我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改

整个流程可以分为以下五个步骤:

  1. 找到前半部分链表的尾节点。

  2. 反转后半部分链表。

  3. 判断是否回文。

  4. 恢复链表。

  5. 返回结果。

执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。

我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。

若链表有奇数个节点,则中间的节点应该看作是前半部分。

步骤二可以使用链表逆置的解决方法来反转链表的后半部分,具体思路和代码可以参考这篇:反转链表

步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。

步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。

// 递归实现链表逆置
 let reverList1 = (head)=>{
    if(head == null || head.next == null){
        return head
    }
​
    let newHead = reverList1(head.next)
    head.next.next = head
    head.next = null
​
    return newHead
 }
​
、// 迭代实现链表逆置
 let reverList2 = (head)=>{
​
    let pre = null
    let cur = head 
    while(cur){
        let next = cur.next
        cur.next = pre
        pre = cur
        cur = next
    }
    return pre
 }
​
// 获取链表后半部分的头部,便于后续对后半部分的逆置和遍历
let half = (head)=>{
    let slow = head
    let fast  = head 
    while(fast.next && fast.next.next){
        slow = slow.next
        fast = fast.next.next
    }
    return slow
}
​
var isPalindrome = function(head) {
    
    if(head == null) return true
​
    let firstList = half(head)
    let secondList = reverList2(firstList.next)
    
    let p1=head
    let p2 = secondList
    let result = true
    // 这里只能是 p2!=null ,而不能是p1!=null,因为前半部分的链表可能比后半部分的链表多一个节点,而这个节点是不需要参与比较的(链表长度为奇数时会多一个节点)
    while(p2){
        if(p1.val != p2.val){
            result =false
            break;
        }
        
        p1 = p1.next;
        p2 = p2.next;
    }
​
    // 还原链表
    firstList.next = reverList2(secondList)
    return result
};

复杂度分析:

时间复杂度:O(n),空间复杂度:O(1)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值