需求:
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
方式一:双指针法
建立一个虚拟节点
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
ListNode pre = null; //建立一个虚拟节点
ListNode cur = head;
ListNode temp;
while(cur != null){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
方式一的错误示例:不建立虚拟节点
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
ListNode pre = head; //唯一改动的地方
ListNode cur = head.next;
ListNode temp;
while(cur != null){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
错误原因分析:
/**
形成的新链表中
5结点的下一个结点更改为4结点(在题设给的链表中,5结点的下一个结点本为null)
4.next = 3; (题设链表中 4.next = 5)
3.next = 2;
2.next = 1;
若按照上述代码提交 新链表的1.next = 2,仍然指向2结点,故形成了环。根本原因在于:一开始的时候pre = head; cur = head.next;这样的话pre.next就一直指向cur,就算反转完之后也存在这个指针,所以就在前两个节点之间形成了死循环。
*/
方式二:递归
递归第一种写法:
class Solution {
//反转整个链表
public ListNode reverseList(ListNode head) {
return reverse(null,head);
}
//反转指定节点curr,并把反转后的节点返回
private ListNode reverse(ListNode pre,ListNode cur) {
if(cur == null){
return pre;
}
ListNode temp = null;
temp = cur.next;
cur.next = pre;
return reverse(cur,temp);
}
}
递归第二种写法(注意这里的head是一个虚拟节点):
//用来反转整个链表
public void reverse(){
//判断当前链表是否为空链表,如果是空链表,则结束运行,如果不是,则调用重载的reverse方法完成反转
if (isEmpty()){
return;
}
reverse(head.next);
}
//反转指定的结点curr,并把反转后的结点返回
public Node reverse(Node curr){
if (curr.next==null){
head.next=curr;
return curr;
}
//递归的反转当前结点curr的下一个结点;返回值就是链表反转后,当前结点的上一个结点
Node pre = reverse(curr.next);
//让返回的结点的下一个结点变为当前结点curr;
pre.next=curr;
//把当前结点的下一个结点变为null
curr.next=null;
return curr;
}
上面两种递归写法对比:
上面两种递归方式可以类比如下情景:在电影院有一排排座位,只有第一排知道自己的座位号(对应在链表中头结点,也就是递归的起始条件,注意这里所说的头结点指的是虚拟节点),现在的需求是每一排都需要知道自己的座位号。
第一种递归方式就是从第一个开始,依次告诉后面一排自己的座位号,每一排座位都在前面一排的座位号加一(递归),到最后一排的时候把自己的座位号返回(递归中的return)。那这样每一排就可以知道自己的座位号了。
第二种方式也是从第一排开始,但是只是告诉后面一排要算座位号(递归)(注意并没有告诉自己的座位号,所以后面一排也没有计算自己的座位号),直到最后一排的时候就把第一排的座位号传给最后一排,最后一排带着座位号返回的时候(递归的return),这样返回的过程中每一排也就知道了自己的座位号。
简而言之:第一种方式是在往下递归的时候就开始计算自己的座位号,第二种方式是回来的计算自己的座位号。