项目地址:https://github.com/SpecialYy/Sword-Means-Offer
题目
输入一个链表,反转链表后,输出新链表的表头。
解析
预备知识
链表之类的题目,一般都会涉及到指针的指向问题,对于这类问题,我们可以通过画图来找思路,空想总是比不过烂笔头。注意链表的三种情况,做到万无一失。
- 输入的链表头指针是null
- 输入的链表只有一个结点
- 输入的链表有多个结点
思路一
我们先画图看看如何反转链表。
通过以上分析我们发现,当对当前节点改变其指向为前一个节点,所以我们需要一个指针pre
来指向前一个节点。改变当前节点的next后,链表会就此断开,我们无法获得后一个将要考察的节点,所以还需一个指针after
指向后一个节点。所以总结如下:
- pre始终指向已反序的最后一个节点
- middle始终指向正在考察节点
- after始终指向待反序的第一个节点,也就是middle之后
当middle指针指向的节点不为空时,我们需要用after来保存它下一个节点,防止链表断开后,无法继续后移。middle的next指向pre实现反序,然后middle和pre同时后移一步即可,此时middle指向下一个待考察的节点,直到middle指向空为止,说明链表已完成反序操作。
/**
* 利用三个指针进行链表反序
* @param head
* @return
*/
public static ListNode ReverseList1(ListNode head) {
//pre始终指向已反序的最后一个节点
ListNode pre = null;
//middle始终指向正在考察节点
ListNode middle = head;
//after始终指向待反序的第一个节点,也就是middle之后
ListNode after = null;
while(middle != null) {
//更新after
after = middle.next;
//对考察的节点进行反序
middle.next = pre;
//更新pre
pre = middle;
//后移middle, 换下一个待考察节点
middle = after;
}
//因为pre始终指向已反序的最后的一个节点
return pre;
}
思路二
还是上面的想法,只不过我们这次充分利用head来代替middle作用,我们赋予head新的语义,使其始终指向已完成反序的最后一个节点。
/**
* 充分利用head指针和外加2个辅助指针
* @param head
* @return
*/
public static ListNode ReverseList2(ListNode head) {
ListNode pre = null;
ListNode after = null;
while(head != null) {
after = head.next;
head.next = pre;
pre = head;
head = after;
}
return pre;
}
思路三
递归思想。
我们使其先走到链表的末尾,确保每次回溯时都返回最后一个节点的指针。同时从倒数第二个结点开始反序。
head.next.next = head;
是指使当前节点的下一个节点指向自己
head.next = null
断开与下一个节点的联系,完成真正的反序操作
如图所示
/**
* 牛逼的递归实现
* @param head
* @return
*/
public static ListNode ReverseList3(ListNode head) {
if(head == null || head.next == null) {
return head;
}
ListNode reverseHead = ReverseList3(head.next);
head.next.next = head;
head.next = null;
return reverseHead;
}