判断一个链表是否为回文结构(三种方法)

判断一个链表是否为回文结构

  • 方法一 : 使用一个栈(O(n)的空间复杂度)
  • 方法二 : 使用快慢指针,以及O(n/2)的空间复杂度
  • 方法三 : 使用快慢指针并反转链表(不需要额外的空间复杂度)

方法一 : 使用一个栈(O(n)的空间复杂度)

这个方法很简单,先遍历一遍链表,把整个链表都压入栈中,然后再遍历一遍,每次从栈顶拿出一个元素进行比较,如果所有元素都相同,则返回true,否则只要有一个不同就返回false。

   //第一种方法 使用O(n)的空间
    public static boolean isPalindromeList_1(Node head){
        if(head == null || head.next == null)return true;
        Stack<Node>stack = new Stack<>();
        Node node = head;
        while(node != null){
            stack.push(node);
            node = node.next;
        }
        node = head;
        while(node != null){
            if(node.value != stack.pop().value){
                return false;
            }
            node = node.next;
        }
        return true;
    }

方法二 : 使用快慢指针,以及O(n/2)的空间复杂度

这个就是在第一种方法的基础上进行简单的改进,首先定义两个指针fast和slow ,每次让fast指针走两步,slow指针走一步,然后当fast指针走完的时候,slow指针刚好来到中点,此时把链表的后半部分压入栈中,然后拿栈中的元素依次和链表的前半部分比较,就可以得到结果。  在coding 的过程中,要注意链表长度奇偶的不同,奇数的时候,slow指针指到中间位置的下一个位置,偶数的时候也要slow指针知道中间位置的下一个位置,一开始的时候slow = head.next,还有就是要注意fast移动的过程中,要做两个判断,防止空指针异常,具体奇偶的过程看下图。
这里写图片描述
这里写图片描述

代码实现:

    //第二种方法 使用O(n/2)的空间
    public static boolean isPalindromeList_2(Node head){
        if(head == null || head.next == null)return true;
        Node fast = head;
        Node slow = head.next; //保证和不管是奇数还是偶数 都会来到中间位置的下一个位置(就是为了把后半部分压入栈中)
        while(fast.next != null && fast.next.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        Stack<Node>stack = new Stack<>();
        while(slow != null){
            stack.push(slow);
            slow = slow.next;
        }
        slow = head;
        while(!stack.isEmpty()){
            if(slow.value != stack.pop().value)return false;
            slow = slow.next;
        }
        return true;
    }

方法三 : 使用快慢指针并反转链表(不需要额外的空间复杂度)

方法三的思想也要使用快指针和慢指针,快指针一次走两步,慢指针一次走一步,当快指针走完的时候,慢指针来到中间的位置(或者中间的前一个位置(偶数的情况(和上面的方法是不同的(上面的是慢指针在中间的后一个位置)))),然后,要用到链表的反转(链表反转不清楚的可以看一下这篇博客),此时,我们将后半部分链表反转,然后使用两个指针分别从 头部和尾部位置开始比较,直到其中一个为空。 当然,不管返回true还是false,我们最后都将反转的链表的结构反转回来。
在coding的过程中,也要注意奇数和偶数的情况,奇数的时候,slow指针正好来到中间结点,偶数的时候来到中间结点的前一个节点。
还有就是反转的细节,使用到了一个额外的next结点变量,在第一次反转的过程中,用来表示当前要反转的结点的下一个节点,还有就是在第一次反转的过程中,我没有额外的再声明pre结点变量,直接使用slow来代替pre结点变量,后面的第二次反转也是重复利用了结点变量。
具体奇偶的过程可以看下图
这里写图片描述
这里写图片描述

代码实现

     //第三种方法,不使用额外的空间
    public static boolean isPalindromeList_3(Node head){
        if(head == null || head.next == null)return true;
        Node slow = head;
        Node fast = head; //这里快指针和慢指针的赋值是为了 不管是奇数还是偶数都保证 快指针走完的之后,慢指针来到中间位置(奇数)或者中间位置的前一个位置(偶数)
        while(fast.next != null && fast.next.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        fast = slow.next; //快指针来到慢指针的下一个节点
        slow.next = null; //慢指针指向null(为了反转链表)
        Node next = null; //代表反转链表的下一个结点

        //反转后半部分
        while(fast != null){
            next = fast.next;
            fast.next = slow; //用slow 来充当反转链表中的pre节点
            slow = fast;
            fast = next;
        }

        next = slow; //反转完之后,slow成为了最后一个结点(可以看做是后半部分的头结点) 此时用next保存这个结点
        fast = head; //这是为了来比较
        boolean res = true;
        while(slow != null && fast != null){  //从两边开始比较
            if(slow.value != fast.value){
                res = false;
                break;
            }
            slow = slow.next;
            fast = fast.next;
        }

        //不管是false还是true都要还原链表
        slow = next.next; //用slow来记录最后一个结点的下一个结点
        next.next = null;  //最后一个节点的下一个节点赋值为null(反转开始(第一步))
        while(slow != null){
            fast = slow.next;
            slow.next = next;  //用next来表示pre
            next = slow;
            slow = fast;
        }
        return res;
    }

再贴上总的测试代码

import java.util.Stack;

/**
 * 判断一个链表是不是一个回文结构
 * 三种方法:
 *      第一种  使用一个栈O(n)的空间复杂度 将整个链表压栈,然后再遍历一遍比对
 *      第二种  使用半个栈(O(n/2))的空间复杂度 快指针一次走两步,慢指针一次走一步 然后将后面的部分压栈
 *      第三种  不使用额外的空间,快指针一次走两步,慢指针一次走一步,走完之后,将后半部分 链表反转,然后使用两个指针从开头和最后开始比较(注意比较完之后恢复链表)
 */
public class IsPalindromeList {

    private static class Node{
        public int value;
        public Node next;

        public Node(int value) {
            this.value = value;
        }
    }

    //第一种方法 使用O(n)的空间
    public static boolean isPalindromeList_1(Node head){
        if(head == null || head.next == null)return true;
        Stack<Node>stack = new Stack<>();
        Node node = head;
        while(node != null){
            stack.push(node);
            node = node.next;
        }
        node = head;
        while(node != null){
            if(node.value != stack.pop().value){
                return false;
            }
            node = node.next;
        }
        return true;
    }

    //第二种方法 使用O(n/2)的空间
    public static boolean isPalindromeList_2(Node head){
        if(head == null || head.next == null)return true;
        Node fast = head;
        Node slow = head.next; //保证和不管是奇数还是偶数 都会来到中间位置的下一个位置(就是为了把后半部分压入栈中)
        while(fast.next != null && fast.next.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        Stack<Node>stack = new Stack<>();
        while(slow != null){
            stack.push(slow);
            slow = slow.next;
        }
        slow = head;
        while(!stack.isEmpty()){
            if(slow.value != stack.pop().value)return false;
            slow = slow.next;
        }
        return true;
    }


    //第三种方法,不使用额外的空间
    public static boolean isPalindromeList_3(Node head){
        if(head == null || head.next == null)return true;
        Node slow = head;
        Node fast = head; //这里快指针和慢指针的赋值是为了 不管是奇数还是偶数都保证 快指针走完的之后,慢指针来到中间位置(奇数)或者中间位置的前一个位置(偶数)
        while(fast.next != null && fast.next.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        fast = slow.next; //快指针来到慢指针的下一个节点
        slow.next = null; //慢指针指向null(为了反转链表)
        Node next = null; //代表反转链表的下一个结点

        //反转后半部分
        while(fast != null){
            next = fast.next;
            fast.next = slow; //用slow 来充当反转链表中的pre节点
            slow = fast;
            fast = next;
        }

        next = slow; //反转完之后,slow成为了最后一个结点(可以看做是后半部分的头结点) 此时用next保存这个结点
        fast = head; //这是为了来比较
        boolean res = true;
        while(slow != null && fast != null){  //从两边开始比较
            if(slow.value != fast.value){
                res = false;
                break;
            }
            slow = slow.next;
            fast = fast.next;
        }

        //不管是false还是true都要还原链表
        slow = next.next; //用slow来记录最后一个结点的下一个结点
        next.next = null;  //最后一个节点的下一个节点赋值为null(反转开始(第一步))
        while(slow != null){
            fast = slow.next;
            slow.next = next;  //用next来表示pre
            next = slow;
            slow = fast;
        }
        return res;
    }

    //打印链表的函数
    public static void printList(Node node) {
        System.out.print("List: ");
        while (node != null) {
            System.out.print(node.value + " ");
            node = node.next;
        }
        System.out.println();
    }


    public static void main(String[] args) {
        System.out.println("---------测试奇数个结点-------------");
        Node head = new Node(1);
        head.next = new Node(2);
        head.next.next = new Node(3);
        head.next.next.next = new Node(2);
        head.next.next.next.next = new Node(1);
        printList(head);
        System.out.println(isPalindromeList_1(head));
        System.out.println(isPalindromeList_2(head));
        System.out.println(isPalindromeList_3(head));


        System.out.println("---------测试偶数个结点-------------");
        Node head2 = new Node(1);
        head2.next = new Node(2);
        head2.next.next = new Node(2);
        head2.next.next.next = new Node(1);
        printList(head2);
        System.out.println(isPalindromeList_1(head2));
        System.out.println(isPalindromeList_2(head2));
        System.out.println(isPalindromeList_3(head2));


        System.out.println("---------测试非回文串-------------");
        Node head3 = new Node(1);
        head3.next = new Node(2);
        head3.next.next = new Node(3);
        head3.next.next.next = new Node(1);
        printList(head3);
        System.out.println(isPalindromeList_1(head3));
        System.out.println(isPalindromeList_2(head3));
        System.out.println(isPalindromeList_3(head3));


        System.out.println("--------------测试一些特殊例子------------");
        Node head4 = new Node(1);
        head4.next = new Node(1);
        printList(head4);
        System.out.println(isPalindromeList_1(head4));
        System.out.println(isPalindromeList_2(head4));
        System.out.println(isPalindromeList_3(head4));



    }
}

运行结果
这里写图片描述

猜你喜欢

转载自blog.csdn.net/zxzxzx0119/article/details/81076158