回文链表

回文链表

解法一:快慢指针(推荐)

题解
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 (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;
    }
}
图示

1.找到前半部分链表的尾节点
在这里插入图片描述
2.反转后半部分链表
在这里插入图片描述

详解
  • 1.首先判断头结点是否为空,如果为空,则该链表为回文链表,如果不为空,继续进行下一步判断。

  • 2.定义两个方法分别为 定位前半部分链表的尾节点 和 反转后半部分节点,返回值类型都为链表。
    【1>.用快慢指针的到前半部分链表的尾节点,如图一,该方法需传参以 head 为头结点的链表,最后返回以 slow 为头结点的链表。 ¥firstHalfEnd¥
    2>.反转链表时,需传参以 slow.next(firstHalfEnd.next) 为头结点的链表,因为 slow 节点属于前半部分链表,我们要反转的是后半部分的链表。最后返回以 prev 为头结点的反转后的后半部分链表。¥secondHalfStart¥】

  • 3.判断是否回文:
    1>.p1指向前半部分链表的头结点;
    2>.p2指向后半部分链表反转后的头结点;
    3>.开始遍历比较两节点的值是否相同(遍历终止条件: p2.next == null)
    4>.返回值类型为布尔类型
    循环遍历之前先定义一个布尔类型的变量 result 值为 true,
    在循环体中如果前后两值不同,result = false,
    之后并不是立刻返回 result 的值 而是等遍历全部结束后先还原链表再返回结果。
    还原链表时,需再次调用反转方法(传入secondHalfStart),再使 firstHalfEnd 与 反转后的头结点相连。
    最后 return result,如果结果为 true 即为回文链表,结果为 false则不是

解法二(递归)

题解
class Solution {
//frontPointer不是局部变量,而是一个可变的类
    private ListNode frontPointer;
//currentNode记录每次调用递归函数的指向
    private boolean recursivelyCheck(ListNode currentNode) {
        if (currentNode != null) {
            if (!recursivelyCheck(currentNode.next)) {
                return false;
            }
            if (currentNode.val != frontPointer.val) {
                return false;
            }
            frontPointer = frontPointer.next;
        }
        return true;
    }

    public boolean isPalindrome(ListNode head) {
        frontPointer = head;
        return recursivelyCheck(head);
    }
}


图示

在这里插入图片描述

详解
  • 1.首先执行函数 isPalindrome,$0 代表链表的第一个节点即 head .建立一个与 head 相同的节点 frontPointer.

  • 2.在调用函数时,计算机必须在栈帧中记录它来自哪里,因为 head 和 currentNode 都是局部变量(frontPointer是一个可变的类),因此在调用recursivelyCheck 函数前,需要把 head 记录在堆栈中。
    帧栈中:
    【1>.mothod:在哪个函数里调用的该函数;
    2>.局部变量指向的地址:指的是调用该函数之前的那个局部变量指向的地址;
    如:在调用 return recursivelyCheck(head);之前的局部变量为 head,head在此之前指向的地址为 $0.把它记录在堆栈之后,再调用 recursivelyCheck(ListNode currentNode) 使 currentNode = head。在再次调用函数之前,在堆栈中记录此时currentNode 指向的地址 $0,然后再调用 recursivelyCheck(ListNode currentNode),使 currentNode = currentNode.next
    3>.Line:在第几行调用的递归函数
    4>.调用函数的那行代码,其中 recursivelyCheck(ListNode currentNode)为.】

  • currentNode的移动:
    在执行函数之前,在帧栈中记录局部变量的信息,调用函数时,currentNode移动,在 currentNode 从前至后的移动过程中,都一直为递归过程,直到 currentNode 指向 null 时, currentNode == null 为递归终止条件,return ture,即 为ture,当 有值时,调用栈弹出,代码回到当初入栈的位置(最后一次调用函数之前),同时currentNode 指向的地址也恢复到调用函数之前的位置(即往前移一位)。此时 !recursivelyCheck(currentNode.next) 结果为false,所以不执行该 if 语句,下一步判断前后节点(frontPointer 和 currentNode )的值是否相同,如果 return true,将该值填充到栈顶的信息中心,然后再回到当初入栈的位置(移除栈顶的信息,并把currentNode指向调用函数之前的位置),以此类推,如果返回结果一直为true,移到栈底时 recursivelyCheck(head)的为true,则最后的返回结果为 true,该链表为回文链表;如果在执行过程中发现前后某一对节点的值不一样,则会return false,再次回到当初入栈时的位置(移除栈顶的信息,currentNode 指向调用函数之前的位置),这时(!recursivelyCheck(currentNode.next)) 为 true.执行if语句,同样返回false,也就是说,当返回false时,就会陷入一直返回false的循环,直到栈中的信息全部移除,此时 frontPointer将不再移动,也不再进行比较,最后移到栈底时 recursivelyCheck(head)的为false,即return false,此链表不是回文链表

难点
  • 掌握局部变量在栈中 记录,弹出,和移除的规律

    用 递归 和 快慢指针 的比较

  • 快慢指针
    在这里插入图片描述

复杂度分析

时间复杂度:O(n)O(n),其中 nn 指的是链表的大小。

空间复杂度:O(1)O(1)。我们只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过 O(1)O(1)。

  • 递归(不推荐)
    在这里插入图片描述
    复杂度分析

时间复杂度:O(n)O(n),其中 nn 指的是链表的大小。
空间复杂度:O(n)O(n),其中 nn 指的是链表的大小。我们要理解计算机如何运行递归函数,在一个函数中调用一个函数时,计算机需要在进入被调用函数之前跟踪它在当前函数中的位置(以及任何局部变量的值),通过运行时存放在堆栈中来实现(堆栈帧)。在堆栈中存放好了数据后就可以进入被调用的函数。在完成被调用函数之后,他会弹出堆栈顶部元素,以恢复在进行函数调用之前所在的函数。在进行回文检查之前,递归函数将在堆栈中创建 nn 个堆栈帧,计算机会逐个弹出进行处理。所以在使用递归时空间复杂度要考虑堆栈的使用情况。

声明

  • 作者:E.L.E
  • <著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。>
  • <欢迎大家评论>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值