算法——判断回文链表

最近在学习一些算法相关的知识,遇到回文链表,接下来将讲解三种不同的方式解回文链表

先创建一个 Node 类

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

方法一

我们知道单链表是有一个 next 指针指向下一个节点,末尾的节点指向 null,我们首先借助栈来判断是否时回文链表
栈最主要的特点:先进后出
实现思路:

  1. 遍历链表,依次将链表的节点入栈
  2. 再次遍历链表,将栈中节点一次弹栈
    1. 如果每一次遍历的节点与弹栈的节点相同,说明是回文链表
    2. 如果在遍历过程中有一次不同,那么就不是回文链表

代码演示

public static boolean isPalindrome1(Node head) {
  if (head == null || head.next == null) {
    return true;
  }
  Stack<Node> stack = new Stack<>();
  Node cur = head;
  // 将链表中所有的节点入栈
  while (cur != null) {
    stack.push(cur);
    cur = cur.next;
  }
  cur = head;
  while (cur != null) {
  // 判断链表中的节点与弹栈的节点是否相同
    if (cur.value != stack.pop().value) {
      return false;
    }
    cur = cur.next;
  }
  return true;
}

方法二

使用快慢指针 + 栈的方式
既然方法一中已经有栈的方法了,那么方法二中为什么还要使用栈呢?原因:方法一中是将所有的链表节点压入栈中,方法二是将一半的节点压入栈中。

实现思路:

  1. 首先使用快慢指针找到中间节点
  2. 将中间节点的右边压入栈
  3. 将栈中的节点一次弹栈,同时从头遍历节点,两者进行节点比较
    1. 若判断节点全部相同,则说明是回文链表,否是不是回文链表

代码演示

public static boolean isPalindrome2(Node head) {
  if (head == null || head.next == null) {
    return true;
  }
  Node slow = head.next;
  Node fast = head;
// 找到中间节点 即 slow 对应的节点
  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.empty()) {
    if (slow.value != stack.pop().value) {
      return false;
    }
    slow = slow.next;
  }
  return true;
}

补充:

在利用快慢指针的时候,我们知道如果链表的长度是奇数个,那么慢指针将会指向最中间的节点,如果是偶数个,慢指针将会指向最中间两个的左边哪个。当我们在面试的时候如果要使用快慢指针,那么慢指针针对题目要求走到那一步更为合适,是指向最中间还是最中间的左边,亦或是最中间的右边,这都需要我们在下面多家练习,练习多了自然在面试中遇到不会出现问题

方法三

使用快慢指针 + 反转链表的方式。降低空间复杂度 O(1)

实现思路:

  1. 使用快慢指针遍历一遍链表,此时慢指针指向链表重点的位置
  2. 将慢指针右边的链表进行反转,指向中间节点,中间节点指向 null
  3. 从左右两端进行链表遍历并进行等值判断
  4. 将原来链表还原成原来的样子

代码实现

/*
	1->2->3<-2<-1
          ⬇
         null
*/
public static boolean isPalindrome3(Node head) {
  if (head== null || head.next == null) {
    return true;
  }
  Node n2 = head;
  Node n1 = head;
  // 使用快慢指针遍历一遍链表
  while (n2.next != null && n2.next.next != null) {
    n2 = n2.next.next;
    n1 = n1.next;
  }
  n2 = n1.next;
  n1.next = null;
  Node n3 = null;
  while (n2 != null) {  // 将右边的部分进行反转
    n3 = n2.next;
    n2.next = n1;
    n1 = n2;
    n2 = n3;
  }
  // 此时 n1 将会指向左边的一个节点
  n3 = n1;    // 保存一下 最左边的节点
  n2 = head;
  boolean res = true;
  while (n1 != null && n2 != null) {
    if (n1.value != n2.value) {
      res = false;
      break;
    }
    n1 = n1.next;
    n2 = n2.next;
  }
  // 将链表再进行还原
  n1 = n3.next;
  n3.next = null;
  while (n1 != null) {
    n2 = n1.next;
    n1.next = n3;
    n3 = n1;
    n1 = n2;
  }
  return res;
}

说明:
上面的代码中,有两次用到了反转链表这个块的知识。我们在刷一些算法题的过程中,有时候也会遇到反转链表1和反转链表2,但是那些就明明白白的告诉你就是一道反转链表的题目。
但是有时候将反转链表的思想利用的其他的算法中,成为解题的一小步的话,那么我们还能不能很好的利用反转链表这块的知识了。也会我们想得到但也并不能很好的给题目解出来。因此经典的基础题目还深入理解透彻,并能轻轻松松的写出来,才可以达到在面试时遇到算法以至于不那么被动

像在接一些比较难的题目的时候,需要用到经典的小思想,就比如上面的反转链表,我称这种为半成品,多掌握一些半成品,并能熟练运用非常有用。

你可能感兴趣的:(链表,算法,数据结构)