Question
Reverse a singly linked list.
Hint
A linked list can be reversed either iteratively or recursively. Could you implement both?
Java Code
//迭代法
public ListNode reverseList(ListNode head) {
ListNode next = null;//原链表中头节点的后继节点
ListNode newHead = null;//反转链表的头节点
while(head != null) {
next = head.next;//暂存原链表中头节点的后继节点
head.next = newHead;//head即将成为反转链表的新头节点,将其后继节点指向反转链表当前的头节点
newHead = head;//反转链表的头节点指向新的头节点
head = next;//原链表的头节点指针移向其下一位,准备下一轮遍历
}
return newHead;
}
//递归法
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) return head;//如果链表为空或已经到达链表尾节点
ListNode newHead = reverseList(head.next);//先反转当前头节点的后继节点
head.next.next = head;//再将当前节点的后继节点的后继节点指向自身
head.next = null;//当前节点的后继节点置空(解除原链表的连接关系)
return newHead;//返回反转链表的头节点
}
说明
迭代法的解题思路应该是比较清晰的,由于所有的交换操作是在原链表中进行的,所以需要用几个变量来暂存转换过程中的节点指针(java中是引用),下面画个示意图简述一下这个过程
假设原链表如下:
1 -> 2 -> 3 -> 4 -> null head
我们从头节点head开始,第1次while循环只是得到反转链表的头节点newhead,并把原链表的head指针移向下一位,得到
1 -> 2 -> 3 -> 4 -> null newhead head
第2次while循环,我们将head指向的节点从链表中抽取出来,添加到newhead之前,使之成为新的newhead,并将head指针下移一位,得到
2 -> 1 -> 3 -> 4 -> null newhead head
如此循环下去,直到head指向null,每一轮循环后newhead总是指向反转之后链表的头节点。
递归法的代码非常简洁,但是算法思想相对复杂些,下面也画个示意图简述其过程
假设原链表如下:
1 -> 2 -> 3 -> 4 -> null head
我们按照递归函数的执行顺序来说明,函数中第一句是判断递归调用的终止条件,第二句是一个递归调用,我们先不进入这个递归内部,只看其执行完之后的效果,也就是说,执行完这句之后,head节点的所有后继节点都已经被反转了,且返回的newHead指向了反转之后的链表的头节点,即
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯↓ 1 4 -> 3 -> 2 -> null head newhead
注意,此时节点1的next仍然指向节点2,因为我们对节点1(此时的head节点)没有做任何操作,继续执行下一句head.next.next = head; 我们得到
|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯↓ 1 4 -> 3 -> 2 ↑____________________| head newhead
也就是我们把head节点的后继节点的next指向了head自身,从而节点1和2之间互为父子,再执行下一句head.next = null; 我们得到
4 -> 3 -> 2 -> 1 -> null newhead
也就是解除节点1和2之间原来的父-子关系,替换成反转之后的子-父关系。
以上就是一次完整的递归调用。至于2->3->4->null如何反转成4->3->2->null则是下一层嵌套递归调用所需完成的任务,每次递归调用执行的操作都是和上面一样的方式。
可能有同学会问,执行完一次递归之后,head指针指向哪里了,其实head指针已经通过head.next传递给其后继节点了,转到下一层递归之中去了。
另外,我们需要注意的是,整个反转的操作是从原链表的尾节点开始的,而不是从头节点开始的(在本例中,就是先把3->4反转为4->3)。因为递归函数的首句就是递归调用(忽略第一句终止条件的判断),所以其后的语句必须等递归调用返回之后才能执行,递归调用会一层一层嵌套深入下去,直到终止条件判断为真,即已经到达原链表的尾节点时,递归才会依次返回,所以递归返回时才会依次从尾节点开始逆向地反转各个节点。这时会有一个奇怪的现象,head指针好像每次传递给了自身的父节点,这是因为递归调用栈具有LIFO的性质,本质上就是递归逐层深入时,先把head节点入栈,再把head的子节点入栈,,,而反转操作是在出栈时执行的,所以先执行子节点的反转,再执行父节点的反转,如果不使用递归或者栈,那么我们是无法逆向遍历单向链表的。