算法通关村第一关--链表经典问题之判断列表是否为回文序列笔记

引言:以下内容均只是个人看法,并无严格经过核对,如有异议,欢迎交流,发现错误,欢迎您的及时指正,感谢!本文所示代码均为java代码

本文中使用的结点定义语句如下

public class listNode {
    public int data;
    public listNode next;
    public listNode(int data) {
        this.data = data;
        this.next=null;
    }
}

单链表的遍历一般从第一个结点依次遍历整个链表,所以第一个结点十分重要,在本文中头节点指的是第一个存放数据的结点,与其他相关文章中关于它的定义可能不同,无需深究,理解相关操作的逻辑原理即可,本质上无较大差别。

回文链表问题

示例1
输入:1-2-2-1
输出:true
示例2
输入:1-2
输出:false
进阶: 能否用O(n)的时间复杂度和O(1)的空间复杂度解决此题

首先对于回文列表问题有两种基本思路:

1.从链表头尾两端进行比较

2.从中间部分向两端开始比较

依然是从常见的数据结构和算法思想

常用的数据结构有数组,链表,队列,栈,HASH,集合,树,堆,常用的算法思想有查找,排序,双指针,递归,迭代,分治,贪心,回溯和动态规划等。

数组法

仅列出从头尾两端开始比较,从中间向两端开始比较采用类似方法,双指针,一个向前,一个向后

把链表存储到数组里面,然后确定数组列表是否回文,因为数组列表方便访问任一下标的元素,我们利用Java中数字列表的size()方法可以确定数组中最后一个元素的下标,然后我们附设两个指针,一个从下标0开始,一个从下标size()-1开始,判断两个指针所指位置存储的数据是否一致,如果不一致则说明不是回文链表。

    /**
     *
     * @param head 所判断链表的头节点
     * @return
     */
    public static boolean isPalindrome_List(listNode head) {
        //创建一个数组列表存放链表中每个结点的数据
        List<Integer> list = new ArrayList<>();
        //判读head是否为空
        if (head == null) {
            return false;
        }
        listNode node = head;
        //把链表中结点数据加到数组里面
        while (node != null) {
            list.add(node.data);
            node = node.next;
        }
        //定义两个指针,front从前向后,back从后向前
        int front = 0;
        int back = list.size() - 1;
        //在有奇数个结点的情况下,front和back可能相等
        while (front <= back) {
            if (!list.get(front).equals(list.get(back))) {
            return false;
            }
            front++;
            back--;
        }
        return true;
    }

利用栈

我们知道栈具有先进后出,后进先出的特点,先按照思路一思考,因为要从两端向中间遍历,所以我们先将链表遍历一次并依次入栈,入栈结束后,同时记录下链表的长度,同时遍历原始链表和栈的出栈操作,比较data是否相同,如果不同就说明不是回文链表,因为此前已经记录链表长度,所以遍历和出栈操作均只进行一半即可。

    /**
     *
     * @param head 所判断链表的头节点
     * @return
     */
    public static boolean isPalindrome_stack(listNode head) {
        //创建栈用来存放链表
        Stack<Integer> stack = new Stack<>();
        if (head == null) {
            return false;
        }
        listNode node = head;
        //记录链表结点的个数
        int length=0;
        //遍历链表结点并压入栈中
        while (node != null) {
            length++;
            stack.push(node.data);
            node = node.next;
        }
        node=head;
        //只出栈一半也只遍历原链表的一半
        while(stack.size()>length/2){
            if(node.data!=stack.pop()){
                return false;
            }
            node=node.next;
        }
        return true;
    }

如果按照思路二思考的话,就是只压栈一半,到达链表长度的一半时,开始出栈,并遍历链表后半部分与出栈元素比较,整个与思路一类似,下面不再呈现代码

递归

这里的递归思路暂时从leetcode借鉴,234题回文链表的官方题解中有提到且有动画更好理解,等之后深刻理解后再填上这块内容

双指针

在遍历链表时候使用两个指针low和fast,fast步长设为2,low步长设为1,这样可以使fast指针到达链表尾的时候low指针恰好为中间(试想一下,我们可以通过设置不同的步长,来使一个到达具体的一个比例的结点处),这样就可以就根据思路一或者二来分为两种情况,第一种根据思路一:反转low指针后面的链表,反转之后进行比较,第二种思路:反转low前面的链表,反转后进行比较

下面仅附有第一种思路的代码(其中关于反转链表的思维仅采取一种方式,之后在第二关的青铜关卡会具体涉及)

    /**
     * 
     * @param head 所判断链表的头结点
     * @return
     */
    public static boolean isPalindrome_doublePoint(listNode head) {
        //定义两个指针fast和low
        listNode fast=head,low=head;
        if(head==null){
            return false;
        }
        //设置fast的步长为2,low的步长为1
        while(fast!=null){
            fast=fast.next.next;
            low=low.next;
        }
        //head2 是将low之后(包括low)的结点反转后得到的链表的头指针,因为是从第一个存有数据的结点开始遍历的
        listNode head2=reverseList(low);
        listNode node=head;
        while(node!=low){
            if(node.data!=head2.data){
                return false;
            }
            node=node.next;
            head2=head2.next;
        }
        return true;
    }

    /**
     * 
     * @param head 从该指针开始后面的结点开始反转(包括该指针)
     * @return
     */
    public static listNode reverseList(listNode head){
        listNode ans=new listNode(-1);
        listNode cur=head;
        //设置虚拟结点ans,每次从待反转的链表种取出一个结点头插入新的链表
        while(cur!=null){
            listNode next=cur.next;
            cur.next=ans.next;
            ans.next=cur;
            cur=next;
        }
        return ans.next;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值