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

题目:给定一个单链表头结点 head,请判断该链表是否为回文结构

例子:1->2->1,返回true;1->2->2->1,返回true;1->2->3,返回false。

要求:如果链表长度为N,时间复杂度达到 O(N),额外空间复杂度达到 O(1)

分析:想到三种方法

  • 压入栈中,然后遍历同时弹栈,看节点内容是否想等

    image-20210217172004608

  • 和第一种一样不过只让链表后半部分压栈,然后遍历比对

    image-20210217171843199

  • 第三种:空间复杂度 O(1)
    step1:先用快慢指针(slow、fast)找到中点(偶数为对称轴前一个,奇数即为中点)

    step2:然后将后面的链表反转:

    ​ 1)偶数时:1 -> 2 -> 3 <- 4 <- 5 <- 6
    ​ 2)奇数时:1 -> 2 -> 3 -> 4 <- 5 <- 6 <- 7

    step3:然后分别遍历左右链表比对即可得出结果
    step4:别忘了将链表复原
    0217191654589

法一:链表全部压栈

    // 把整个链表压栈,然后遍历链表和弹栈结果是否一致
    public static boolean isPalindrome1(ListNode head) {
        if (head == null || head.next == null) return true;
        Stack<ListNode> stack = new Stack<>();
        ListNode tmp = head;
        while (tmp != null) {
            stack.push(tmp);
            tmp = tmp.next;
        }
        while (head != null && stack.pop().val == head.val) {
            head = head.next;
        }
        return head == null ? true : false;
    }

法二:一半压栈

如何找到中点:快慢指针 /slow/fast,条件如何取。有三种常见初始状态(但至少要两个节点)

  1. slow = fast = head:都指向头结点
  2. slow = head; fast = head.next; :慢指向头结点,快指向第二节点
  3. slow = head.next; fast = head; :慢指向第二节点,快指向头结点

然后依次讨论判断的条件是 fast.next!= null && fast.next.next != null

  •   slow = fast = head; 时
      
      EX1: 1 2	开始都指向第 1
      明显 fast 一步都不能动
      指向对称轴前一个
      
      EX2: 1 2 3 4	
      由判断条件得出fast可以走1步,那么slow指向2
      指向对称轴前一个
      
      EX3: 1 2 3 4 5 6
      fast可走2步,slow指向3
      对称轴前一个
      
      EX4: 1 2 3
      fast可以走1步,slow指向2
      指向中点位置
       
      EX5: 1 2 3 4 5
      fast可以走2步,slow指向3
      指向中点位置
      
      EX6: 1 2 3 4 5 6 7
      fast可走3步,slow指向4
      指向中点位置
    
  •   slow = head; fast = head.next; 时
      
      EX1:链表:1 2	slow指向1,fast指向2
      明显 fast 一步都不能动
      然后此时指向对称轴前一个
      
      EX2:链表:1 2 3 4	
      由判断条件得出fast可以走1步,那么slow指向2
      也是指向对称轴前一个
      
      EX3: 1 2 3 4 5 6
      fast可走2步,slow指向3
      对称轴前一个
      
      EX4:链表:1 2 3
      fast可以走0步,slow指向1
      指向中点前一个位置
       
      EX5:链表:1 2 3 4 5
      fast可以走1步,slow指向2
      指向中点前一个位置
      
      EX6: 1 2 3 4 5 6 7
      fast可走2步,slow指向3
      指向中点位置前一个位置
    
  •   slow = head.next; fast = head; 时
       
      EX1:链表:1 2	slow指向2,fast指向1
      明显 fast 一步都不能动
      然后此时指向对称轴后一个
      
      EX2:链表:1 2 3 4	
      由判断条件得出fast可以走1步,那么slow指向3
      也是指向对称轴后一个
      
      EX3: 1 2 3 4 5 6
      fast可走2步,slow指向3
      对称轴后一个
      
      EX4:链表:1 2 3
      fast可以走1步,slow指向3
      指向中点后一个位置
       
      EX5:链表:1 2 3 4 5
      fast可以走2步,slow指向4
      指向中点后一个位置
      
      EX6: 1 2 3 4 5 6 7
      fast可走3步,slow指向5
      指向中点后一个位置
    

最后得出结论:判断条件是 fast.next!= null && fast.next.next != null

  1. slow = fast = head;

偶数:对称轴前一个

奇数:中点

  1. slow = head; fast = head.next;

偶数:对称轴前一个

奇数:中点前一个

  1. slow = head.next; fast = head;

偶数:对称轴后一个

奇数:中点后一个

// 很容易得出,我们需要后一半压栈所以选择第三种快慢指针方案
    public static boolean isPalindrome2(ListNode head) {
        if (head == null || head.next == null) return true;
        ListNode slow = head.next;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 此时slow指向中点或者对称轴的下一个
        Stack<ListNode> stack = new Stack<>();
        while (slow != null) {
            stack.push(slow);
            System.out.print(slow.val + "_");
            slow = slow.next;
        }
        while (!stack.isEmpty()) {
            if (head.val != stack.pop().val) {
                return false;
            }
            head = head.next;
        }
        return true;
    }

法三:

这里需要用到反转链表的模板,模板如下

// 加入 head 为链表的头结点
	ListNode pre = head;		// 即指向头结点
	ListNode last = head.next;	// 即指向头结点下一个
	ListNode tmp = null;
	pre.next = null;
	while (last != null) {
        tmp = last.next;
        last.next = pre;
        pre = last;
        last = tmp;
    }
// 最后循环完后的结果是 pre 指向反转的首节点,last、tmp 指向 null
    // 用偶数个举例,如链表为 1 -> 2 -> 3 -> 4 -> 5 -> 6
    public static boolean isPalindrome3(ListNode head) {
        if (head == null || head.next == null) return true;

        // step1:找到中点位置,偶数为对称轴前一个,奇数为中点
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }

        // step2:反转后半部分链表
        // 此时 slow 指向中点或者对称轴的前一个,也就是相当于反转部分的头结点
        // 即 slow 指向 3,接下来要反转后面部分得到:1 -> 2 -> 3 <- 4 <- 5 <- 6
        fast = slow.next;
        ListNode tmp = null;
        slow.next = null;
        while (fast != null) {
            tmp = fast.next;
            fast.next = slow;
            slow = fast;
            fast = tmp;
        }
        tmp = slow; // 指向最后一个节点(或者后部分链表的头结点)这里后面会用到

        // step3:将两边链表分别遍历比对
        // 此时slow指向反转链表的首节点,也就是原链表的最后一个
        // 1 -> 2 -> 3 <- 4 <- 5 <- 6 slow 指向 6
        // 然后我们依次遍历两边的链表
        fast = head;
        boolean ans = true;
        while (fast != null && slow != null) {
//            System.out.print("fast:" + fast.val + ",slow:" + slow.val + "\n");
            if (fast.val != slow.val) {
                ans = false;
                break;
            }
            fast = fast.next;
            slow = slow.next;
        }

        // step4:链表复原
        // 此时 slow 指向 3,fast 指向 null
        // 如果是奇数个 slow、fast 都指向 null,所以要重新赋值
        slow = tmp;
        fast = slow.next;
        while (fast != null) {
            tmp = fast.next;
            fast.next = slow;
            slow = fast;
            fast = tmp;
        }
        return ans;
    }

最后附上全部代码

package LinkedList;

import java.util.Stack;

/**
 * @ Date: 2021/2/10 - 13:19
 * @ Description: LinkedList
 * @ Version: 1.0
 */
// https://leetcode-cn.com/problems/palindrome-linked-list-lcci/
public class isPalindromeLinkedList_回文链表 {

    static class ListNode {
        int val;
        ListNode next;
        ListNode(int x) {
            val = x;
        }
    }

    static void printLinkedList(ListNode head) {
        System.out.print("Linked List: ");
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }

    // 把整个链表压栈,然后遍历链表和弹栈结果是否一致
    public static boolean isPalindrome1(ListNode head) {
        if (head == null || head.next == null) return true;
        Stack<ListNode> stack = new Stack<>();
        ListNode tmp = head;
        while (tmp != null) {
            stack.push(tmp);
            tmp = tmp.next;
        }
        while (head != null && stack.pop().val == head.val) {
            head = head.next;
        }
        return head == null ? true : false;
    }

    // 半个链表压栈
    public static boolean isPalindrome2(ListNode head) {
        if (head == null || head.next == null) return true;
        ListNode slow = head.next;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 此时slow指向中点或者对称轴的下一个
        Stack<ListNode> stack = new Stack<>();
        while (slow != null) {
            stack.push(slow);
//            System.out.print(slow.val + "_");
            slow = slow.next;
        }
        while (!stack.isEmpty()) {
            if (head.val != stack.pop().val) {
                return false;
            }
            head = head.next;
        }
        return true;
    }

    // 用偶数个举例,如链表为 1 -> 2 -> 3 -> 4 -> 5 -> 6
    public static boolean isPalindrome3(ListNode head) {
        if (head == null || head.next == null) return true;

        // step1:找到中点位置,偶数为对称轴前一个,奇数为中点
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }

        // step2:反转后半部分链表
        // 此时 slow 指向中点或者对称轴的前一个,也就是相当于反转部分的头结点
        // 即 slow 指向 3,接下来要反转后面部分得到:1 -> 2 -> 3 <- 4 <- 5 <- 6
        fast = slow.next;
        ListNode tmp = null;
        slow.next = null;
        while (fast != null) {
            tmp = fast.next;
            fast.next = slow;
            slow = fast;
            fast = tmp;
        }
        tmp = slow; // 指向最后一个节点(或者后部分链表的头结点)这里后面会用到

        // step3:将两边链表分别遍历比对
        // 此时slow指向反转链表的首节点,也就是原链表的最后一个
        // 1 -> 2 -> 3 <- 4 <- 5 <- 6 slow 指向 6
        // 然后我们依次遍历两边的链表
        fast = head;
        boolean ans = true;
        while (fast != null && slow != null) {
//            System.out.print("fast:" + fast.val + ",slow:" + slow.val + "\n");
            if (fast.val != slow.val) {
                ans = false;
                break;
            }
            fast = fast.next;
            slow = slow.next;
        }

        // step4:链表复原
        // 此时 slow 指向 3,fast 指向 null
        // 如果是奇数个 slow、fast 都指向 null,所以要重新赋值
        slow = tmp;
        fast = slow.next;
        while (fast != null) {
            tmp = fast.next;
            fast.next = slow;
            slow = fast;
            fast = tmp;
        }
        return ans;
    }

    public static void main(String[] args) {
        ListNode head = null;
        printLinkedList(head);
        System.out.print(isPalindrome1(head) + " | ");
        System.out.print(isPalindrome2(head) + " | ");
        System.out.println(isPalindrome3(head));

        head = new ListNode(1);
        head.next = new ListNode(2);
        printLinkedList(head);
        System.out.print(isPalindrome1(head) + " | ");
        System.out.print(isPalindrome2(head) + " | ");
        System.out.println(isPalindrome3(head));

        head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(1);
        printLinkedList(head);
        System.out.print(isPalindrome1(head) + " | ");
        System.out.print(isPalindrome2(head) + " | ");
        System.out.println(isPalindrome3(head));

        head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        printLinkedList(head);
        System.out.print(isPalindrome1(head) + " | ");
        System.out.print(isPalindrome2(head) + " | ");
        System.out.println(isPalindrome3(head));

        head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(2);
        head.next.next.next.next = new ListNode(3);
        printLinkedList(head);
        System.out.print(isPalindrome1(head) + " | ");
        System.out.print(isPalindrome2(head) + " | ");
        System.out.println(isPalindrome3(head));

        head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(3);
        head.next.next.next.next.next = new ListNode(2);
        head.next.next.next.next.next.next = new ListNode(1);
        printLinkedList(head);
        System.out.print(isPalindrome1(head) + " | ");
        System.out.print(isPalindrome2(head) + " | ");
        System.out.println(isPalindrome3(head));

    }
}
// Output
Linked List: 
true | true | true
Linked List: 1 2 
false | false | false
Linked List: 1 2 1 
true | true | true
Linked List: 1 2 3 4 
false | false | false
Linked List: 1 2 3 2 3 
false | false | false
Linked List: 1 2 3 4 3 2 1 
true | true | true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值