题目
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
限制:
0 <= 节点个数 <= 5000
其中,链表的结构为:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
思路
1、用头插法实现链表的反转
刚开始看到这道题的思路,就是最简单、最暴力的解法----再定义一个头节点,用头插法实现链表的反转。但是,在实现的过程中,花费了不小功夫来调bug。主要的原因,还是读题目不够清晰!
题目说的是:输入一个链表的头节点,要求实现链表的反转。
但是,需要注意的是,给的示例为:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
给出的示例表明,其实这个 输入的“链表的头节点”,并不是真正的头节点 => 实际上,输入的链表,是没有头节点的,所以输入的head,是该链表中,第一个节点不假,但是该节点的val属性,即:在链表不空的情况下,head.val 存储的是有数据的!!!!!!
所以,代码实现为:
class Solution {
public ListNode reverseList(ListNode head) {
/*
实际上进行的操作:将head对应的单链表,进行遍历,并将遍历到的节点按照头插法,插入到新的头节点reverseHead对应的单链表中
注意:输入的单链表实际上是不带头节点的,head就是实际存储数据的第一个节点
*/
if(head == null){
//一个节点都没有的情况下,直接返回原来的头节点即可
return head;
}
//定义新的"头节点reverseHead,
ListNode reverseHead = new ListNode(head.val);
//定义一个辅助节点,用于帮助在head对应的链表中进行遍历
//cur实际上对应的是head对应的单链表中,head节点后的第一个节点
ListNode cur = head.next;
ListNode next = null;//用于:帮助实现head所在链表中的cur的后移
//循环遍历的过程
while(cur != null ){//当head对应的链表中,除了head节点之外还有其他节点时
//暂存当前节点cur的下一个节点
next = cur.next;
//实现在reverseHead的链表中的头插法"reverseHead是真正的带头节点"
cur.next = reverseHead;
reverseHead = cur;
//在head的链表中,要进行实现遍历的必要操作------后移
cur = next;//这里一定要有next这个变量的存在,不然在进行过reverseHead中的头插操作后,会找不到head中原来的cur后面的那个节点
}
return reverseHead;
}
}
2、手动实现相邻节点直接链接的反向(也叫双指针法)
----时间复杂度、空间复杂度,均要比 1 的直接头插法实现的方式好一些
- 使用双指针(实际上是有三个指针,一个用来遍历,其余两个都是作为辅助指针)来解题:
- 定义三个指针:pre、cur、next,其中cur用来遍历,而pre.next==cur,next=cur.next;
-
将原来的单链表的整个方向进行转换,即:
- 将pre.next=cur改为:pre=cur.next;
- 然后,将三个指针整体往正在进行的遍历的方向移动一个单位:
- ---->pre=cur,cur=next,next=next.next
-
简言之,pre指针是用来辅助 实现链表方向转换的一个变量;
- =>而next指针是用来辅助遍历的一个变量(因为一个节点的next域只能存储一个其他节点,所以,如果cur建立起一个新的next域指向的pre的链连接,就需要一个next指针来保存该cur节点原来的next域指向的节点);
- =>而cur指针,就是用来进行遍历的指针,它既可以用来建立逆向的链表连接,又可以作为三个指针向前遍历的一个参考基准
- 而控制循环进行的条件为:cur!=null,即:pre要作为最终返回的“头节点”
所以,代码实现为:
class Solution {
public ListNode reverseList(ListNode head) {
//链表一个节点都没有或者只有一个节点,没有反转的必要
if( head==null || head.next==null){
return head;
}
//此时链表至少有两个节点
ListNode cur = head.next;
ListNode pre = head;
ListNode next = null ;
//head节点最终在实现反转之后是链表的末尾
head.next = null;
while(cur != null){//当cur是最后一个节点时,循环终止
//在建立新的链接之前,先保留下来原来的链接
next = cur.next;
//建立反向的链接
cur.next = pre;
//跟新剩余的2个指针
pre = cur;
cur = next;
}
return pre;
}
}
3、 使用递归的方法实现反转
----是一个提升吧,强大又难搞的递归:
*
-
可以这样对问题进行分析:把大问题划分成小问题:
- 1)假设链表的其余部分已经实现反转,那么如何实现前面的部分的反转?
----cur.next.next = cur; - 2)递归的结束条件是什么?
----cur.next == null; - 3)实现递归的方法中,如何确定1)、2)两步骤的顺序?
实际上是跟树的遍历的顺序是一样的,应该在递归结束之后,回溯的过程中,实现链接方向的改变,不然的话,会由于链接方向的改变,导致还没有遍历到的节点丢失(因为没有节点链接向该节点而丢失) - 4)需要注意的是,要将最初的head节点的next域的值,设置为null。不然会出现 环。
Attention:回溯时,每到一个节点,就将该节点当做新链表的尾节点来进行处理 - 5)以上几步的顺序为:2)=> 1) => 4)
- 1)假设链表的其余部分已经实现反转,那么如何实现前面的部分的反转?
-
其实,当该链表 不包含节点 或者 只包含一个节点的情况下,可以不用调用递归,在刚开始,直接return head。
- ------可以和递归的终止条件 合并
所以,代码实现为:
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
//1、没有节点或者只包含一个节点,不用反转
//2、也作为递归的终止条件
return head;
}
//当前节点不是递归终点时,进行的操作
ListNode newHead = reverseList(head.next);//递归调用
head.next.next = head;
head.next = null;
return newHead;
}
}
总结一下数据结构中链表问题 解决时候的真理: