力扣-->#234. 回文链表-->链表-简单(双指针、数组列表、递归、快慢指针)

本文介绍了判断回文链表的三种方法:1) 双指针法,通过复制链表值到数组再比较;2) 递归法,通过递归反转后半段链表并逐个比较;3) 快慢指针法,反转后半段链表再逐个比较。每种方法的时间复杂度和空间复杂度都有所不同,适合不同的场景。
摘要由CSDN通过智能技术生成

234. 回文链表

题目描述:
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:

输入:head = [1,2,2,1]
输出:true

示例 2:

输入:head = [1,2]
输出:false

提示:
链表中节点数目在范围[1, 105] 内
0 <= Node.val <= 9

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

感觉这道题不难,但是自己写了一下运行后却超出时间范围了,有点找不出是什么问题,应该是反转链表时出了问题,后续会继续对齐进行完善。
思路上是:
1.新建一个节点temph,用于存储原来的头结点指针;
2.新建一个节点tempt,用于反转链表时保留中间结点;
3.反转链表,得到以tail为头结点的一个新链表;
4.head逐个向后移动,tail逐个向前移动,两两进行比较,若相等,继续比较;若不等,返回false。
代码如下:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {//双指针
        ListNode tail=head; //从头开始遍历,以找到尾节点
        ListNode tempt=head;//用于保存反转链表时的中间结点
        ListNode temph=head;//保留头结点的指向
        while(temph!=null){//反转链表,这段代码有问题!陷入了循环
            tempt=tail;     //临时的尾指针保留住上一次反转之后出现的尾指针
            tail=temph.next;//真实的尾指针向后移一位
            tail.next=tempt;//尾指针的下一个节点指向保留下来的那个节点
            temph=temph.next;//临时头指针向后遍历一位
        }
        while(head!=tail){
            if(head.val!=tail.val){
                return false;
            }
            head=head.next;
            tail=tail.next;
        }
        return true;
    }
}

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

这是官网中的方法一,其思路为:
由于链表相较于数组的特殊性,链表无法直接查询某个值,而需通过遍历,且无法逆序查找,只能单向查找,而数组可以在O(1)内获取某个值,所以综合考虑使用数组列表先保存原链表中的值,再在数组列表中用双指针一头一尾同时逐个向中靠拢,逐个比较数组中的值;

注意:
由于我在平时的使用中缺乏注意数组列表的使用细节,所以有以下地方需要着重注意区分:
1.数组列表的创建不需要向数组那样有确定的长度,可以直接List<Integer> arrList=new ArrayList<Integer>();多态创建,注意别丢了第二个泛型;
2.数组列表中获取值为:arrList.get(i),而非平时链表中的linkList.val;
3.获取数组长度的方式为arrList.size(),而非普通数组中的arr.length。

执行结果:通过
执行用时:8 ms, 在所有 Java 提交中击败了40.24%的用户
内存消耗:50.9 MB, 在所有 Java 提交中击败了28.78%的用户
通过测试用例:85 / 85

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {    //将值复制到数组中后用双指针法
        List<Integer> arrList=new ArrayList<Integer>();
        ListNode currentNode=head;
        while(currentNode!=null){   //注意是当前节点,不要习惯性使用下一个结点来判断
            arrList.add(currentNode.val);
            currentNode=currentNode.next;
        }

        int left=0;
        int right=arrList.size()-1;
        while(left<right){
            if(!arrList.get(left).equals(arrList.get(right))){//注意数组列表获取值的方法,与.val区分
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

方法二:递归
这是官网中的方法二,使用递归的巧妙之处及总体思路在于:
当递归至最后一步后开始跳出递归时,正好类似链表反转后的结果,在此处进行首尾逐渐向中靠拢的比较方式就很优雅!我们在方法外部还定义了一个指向头结点的全局变量,用于与递归方法中的递归跳出值进行比较,本质上就是在进行正向和逆向迭代匹配。
详细思路:
1.递归方法中,先对结点判空,为空则表示已到达链表末端,直接返回;非空则表示未到链表末端,仍需继续递归直到找到链表末端;
2.找到末端之后,返回true,开始跳出递归,此时直接执行第二个if(因为是由此开始递归的,所以跳出递归时也是从这开始执行),if(!true)意味着不满足条件,直接开始判断值,若值一直相等(一边相等会一边跳出递归)直至最后跳出所有递归,最终返回true,即为回文;
3.若在第三个if判断值是否相等的时候出现了两值不等的情况,则在此返回false,那么跳出那次递归之后第二个if为if(!false)满足该循环,之后的所有跳出递归都在此满足条件,最终返回false,即不为回文。

执行结果:通过
执行用时:16 ms, 在所有 Java 提交中击败了7.92%的用户
内存消耗:55.4 MB, 在所有 Java 提交中击败了12.42%的用户
通过测试用例:85 / 85

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    private ListNode front; //外部全局变量,由于与递归后返回的值作比较

    public boolean isPalindrome(ListNode head) {    //题给方法
        front=head;
        return recursivelyCheck(head);  //调用递归方法,传进头指针,返回得到最终值
    }

    public boolean recursivelyCheck(ListNode current){
        if(current!=null){          //当前节点的非空
            //传进下一个结点,先判断是否为空若,
            //为空说明已递归至链表末端,返回true-->if(!true)-->不执行,去到下一个if
            //不为空则继续递归调用直至找到最后一个结点-->去到上一个注释
            if(!recursivelyCheck(current.next)){
//由此递归,也由此跳出递归,若跳出时为false(下一注释),满足此if,此后均在此满足,即该递归最终返回false,非回文
                return false;
            }
            //判断值是否相等,若不等,返回false,并逐步跳出递归,最后返回false
            if(front.val!=current.val){
                return false;
            }
            front=front.next;
        }
        return true;
    }
}

方法三:快慢指针
这是官网中的方法三:
1.平分链表:该方法利用慢指针一次走一个结点,快指针一次走两个结点,当快指针的下一个或下下个指向为null时,说明链表遍历完毕,此时快指针遍历到末尾,而慢指针正好位于链表中央;
2.反转并比较:将后半段链表反转后,同时从两段链表中从前往后遍历,一一比较,最后返回得到的结果;
3.恢复链表:由于通常情况下,提出比较的人并不希望自己的链表结构受到影响,所以比较后还需要将链表反转回来,只需要再次将对应段头结点传入反转链表的方法中即可。

执行结果:通过
执行用时:6 ms, 在所有 Java 提交中击败了60.32%的用户
内存消耗:48.2 MB, 在所有 Java 提交中击败了82.32%的用户
通过测试用例:85 / 85

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
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 = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
}

平平无奇小白程序媛一枚,欢迎各位大佬交流指教,如有不正确的地方,欢迎留言改正,谢谢!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值