链表是常用的线性数据结构。链表的结构就好像是一列火车,每个车厢都代表一个结点,结点与结点依次相连。单链表是所有链表类型中最直接的,单链表中每个节点都存了指向下一个节点的指针。单链表就像一列只能从车头走到车尾的列车。
单链表vs双链表:双链表比单链表多一个指向前一个元素的指针。双链表可以来回的遍历整个链表,所以在遍历结点时更加高效。既然双链表如此高效,我们为什么还需要用单链表呢?单链表结构更加简单,所以空间限制的情况下我们更倾向于用单链表。在面试中,链表问题都是以单链表的形式存在的,因为单链表可以考验从业人员处理边界问题和优化空间的能力。
链表回文判断:回文就是往前读和后读都是一样的,比如1->2->1。这道题如果没有空间限制的话,实现很简单。最容易想到的方法是将单链表的元素全部存到栈里面。通过栈先进后出的特点,将元素给逆序。链表如果是1->2->3放入到栈中,那我们取出来就是3,2,1。如何做到时间复杂度为O(N), 额外空间复杂度为O(1)?
栈方法优化:回文有一个特性,回文是两边对称的,比如1->2->1 这个回文链表是以2为中心对称的。我们可以通过快慢指针来找到链表中心点。通过快指针移动2位慢指针移动1位,让慢指针停在链表中点的位置。这样我们就只需要将中点后面的元素存入栈中,可以节省原来方法一半的空间复杂度。但是由于大O不考虑常数项,空间复杂度还是O(N)。
中点逆序法:这个方法和优化过的栈方法有异曲同工之妙,都是利用了回文两边对称的特性。通过把中点到尾端的元素重新逆向排列,来比较两边的元素。本质上就是把一个从左到右的链表结构变成一个双头的由外向内的结构,比如1->2->3 变成1->2<-3。 但是这里面有很多细节要注意,我们的链表大小可以是双数,也可以是单数。如果是单数链表慢指针恰好停在了链表的中间元素上,而双数的链表慢指针会停在了中间元素之前的位置。可以通过快指针是否为null来判断双数和单数,然后处理这两种不同的情况,代码如下:
class LNode{
public int value;
public LNode next;
public LNode(int number) {
value = number;
}
}
public class IsPalindromeList {
public boolean buildPalindrome(LNode root) {
if(root==null) {
return true;
}
//create fast and slow node
LNode fast = root;
LNode slow = root;
//fast move two position while slow more one
//error mistakes about &&
while(fast.next!=null&&fast.next.next!=null) {
fast = fast.next.next;
slow = slow.next;
}
if(fast.next!=null) {
LNode temp = slow;
slow = slow.next;
temp.next = null;
}
fast = slow.next;
slow.next = null;
LNode superfast = fast.next;
while(superfast!=null) {
fast.next = slow;
slow = fast;
fast = superfast;
superfast = superfast.next;
}
fast.next = slow;
LNode left = root;
LNode right = fast;
while(left!=null) {
if(left.value != right.value) {
return false;
}
left = left.next;
right = right.next;
}
return true;
}
public static void main(String[] args) {
IsPalindromeList ip = new IsPalindromeList();
LNode node1 = new LNode(1);
LNode node2 = new LNode(2);
LNode node3 = new LNode(3);
LNode node4 = new LNode(2);
LNode node5 = new LNode(1);
LNode node6 = new LNode(3);
node1.next = node2;
node2.next = node4;
node3.next = node4;
node4.next = node5;
node5.next = node6;
System.out.println(ip.buildPalindrome(node1));
}
}
总结:链表问题往往是用来考察开发人员处理边界和优化空的能力。单链表里最关键的技巧就是快慢指针。大部分单链表的题都可以用快慢指针来优化,比如找到单链表里倒数第N个元素、单链表分治排序,单链表是否有环等。