单链表回文判断
- 2020 年 1 月 23 日
- 筆記
判断一个单链表是否为回文链表目前有两种实现思路。一种是通过数组记录前半部分与后半部分依次比较,一种是找到链表中间结点,将左半部分反转与右半部分依次比较,下面详细介绍。
基于数组
用数组存储链表前半段的值,使用快慢指针法来进行截取。
public boolean isPalindromeByArray(Node node){ if (node == null) return false; Node fast = node; Node slow = node; if (node.next == null) return true; /** * 找到中间结点,同时用数组逆插左侧元素并保存。 * nodeList.add(0, slow.data); 在指定位置插入元素,原位置及之后的依次向右移动一位。 */ List<Integer> nodeList = new ArrayList<Integer>(); nodeList.add(0, slow.data); while (fast.next != null && fast.next.next != null ) { fast = fast.next.next; slow = slow.next; nodeList.add(0, slow.data); } if (fast.next != null){ // fast.next不为空,数据为偶数。 slow = slow.next; } Node curr = slow; int i = 0; while (null != curr){ if (curr.data != nodeList.get(i)){ return false; } curr = curr.next; i++; } return true; }
nodeList.add(0, slow.data);
插入是比较耗时的,毕竟每次插入都需要将之前的依次向右移动一位。所以可以考虑用栈实现。
基于栈的回文判断
思路同基于数组的,但因为免去了保存新结点的右移操作,所以比使用数组保存左侧数据的方式高效一些。
/** * 基于栈比较是否为回文--半栈 * @param node * @return */ public boolean isPalindromeByStack(Node node){ if (node == null) return false; Node fast = node; Node slow = node; if (node.next == null) return true; /** * 找到中间结点,同时用栈保存左侧数据。 * */ Stack<Integer> nodeList = new Stack<Integer>(); nodeList.push(slow.data); // 1 2 3 while (fast.next != null && fast.next.next != null ) { fast = fast.next.next; slow = slow.next; nodeList.push(slow.data); // 1 2 3 } if (fast.next != null){ // fast.next不为空,数据为偶数。 slow = slow.next; } Node curr = slow; int i = 0; while (null != curr){ if (curr.data != nodeList.pop()){ return false; } curr = curr.next; i++; } return true; }
基于链表反转的回文判断
该方案的思路是:通过快慢指针获取中间结点,反转中间结点左侧部分,遍历并对比反转后左右两侧结点的数据判是否为回文。
只需要固定的若干个临时变量,不需要额外开辟空间
时间复杂度为O(n),空间复杂度为O(1)
该方式可以略作简化将反转左侧部分在查中间结点时同时完成。这里为了方便阅读,分开了查找中间结点和反转左侧结点的步骤,不再实现该优化。
/** * 不含逻辑头节点的回文链表判断 * 思路: * 遍历一遍链表,得到链表长度n,根据长度的奇偶,找到中间节点,将左半边的链表反转,然后从中间节点分两个方向向左右两边遍历,是否是回文; * * 对左半部分链表进行反转,还原为最初的链表(目前函数未实现左半部分链表还原) * * * 1 2 3 4 5 8 9 slow 4 fast 9 * 1 2 3 4 5 8 slow 3 fast 5 * @return */ public boolean isPalindrome(Node head){ if (head == null) return false; PrintUtill.println("开始执行找到中间节点"); Node fast = head; Node slow = head; if (fast.next == null){ System.out.println("只有一个元素"); return true; } /** * 快的一次走两步,慢的一次走一步那么最后快的结束了慢的走了一半 */ while (fast.next !=null && fast.next.next != null){ fast = fast.next.next; slow = slow.next; } Node leftLink = null; Node rightLink = null; /** * 获取中间结点左右两部分,反转左侧部分。 * fast.next == null :节点数目为奇数,且slow一定为为中间节点 * fast.next.next == null :节点数目为偶数,slow、slow.next 均为中心结点 */ rightLink = slow.next; if (fast.next == null){ leftLink = inverseLinkListToEnd(slow).next; }else { leftLink = inverseLinkListToEnd(slow); } /** * 从此处开始同步向两边比较 */ return TFResult(leftLink, rightLink); } /** * 比较是否为回文 * @param left * @param right * @return */ public boolean TFResult(Node left, Node right){ while (left != null && right != null){ if (left.data != right.data){ return false; } left = left.next; right = right.next; } return true; } /** * 反转中间结点的左半部分,返回以end结点为头节点的链表。 * @param end * @return */ public Node inverseLinkListToEnd(Node end) { Node pre = null; Node cur = head; Node next = null; /** * 反转中间结点之前的结点 */ while (cur!=end){ next = cur.next; cur.next = pre; pre = cur; cur = next; } cur.next = pre; return cur; }