题目描述
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
完整代码参考:GitHub 代码
题解
此题难度定级为简单,主要考察链表的基本操作。
解法1:递归
递归的方式代码实现较为简洁,但是过程稍微绕脑,分析如下:
0. 初始化 head => 1 => 2 => 3 => 4 => 5 => null,递归找出当前为 null 或 其下一个节点为 null 的节点,此时返回的节点是 5=> null;
1. 在递归条件中,需要判断 head 的下一个节点是否为 null,故 reverseList(head.next) 递归的入参为 head.next,否则将造成死循环(因为当前节点 head 及其下一个节点不为 null);
2. 定义新节点 newHead 指向递归的结果节点,此时 newHead =>5 => null,此时的 head => 4 => 5 => null;
3. 设置新节点(newHead )的下一个节点为新节点的上一个节点。什么意思呢?就是将 head 的尾节点再次指向 head,即构成一个无限的闭环,调试代码如下:
此时 head 与 newHead 指向的链表均构成环形链表。(为什么 newHead 指向的链表也是环形链表呢?因为 head 与 newHead 在递归的过程中自始至终操作的均是同一个链表。)
此时 head => 4 => 5 => 4 => 5 => 4 => .....
newHead => 5 => 4 => 5 => 4 => .....
4. 设置 head.next = null,即此时 head =>4 => null,而 newHead => 5 => 4 => null;如下图:
5. 递归过程依次向前推进,head 变化过程如下:
head => 3 => 4 => 3 => 4 => .....
head => 2 => 3 => 2 => 3 => .....
head => 1 => 2 => 1 => 2 => .....
head => null
而 newHead 变化过程如下:
newHead => 5 => 4 => null
newHead => 5 => 4 => 3 => null
newHead => 5 => 4 => 3=> 2 => null
newHead => 5 => 4 => 3=> 2 => 1 => null
结束递归,返回 newHead,反转链表完成。
递归方式题解完整代码如下:
/**
* @Description: 反转链表 递归方式
* 输入: 1->2->3->4->5->NULL
* 输出: 5->4->3->2->1->NULL
* @Date: 2019/12/4 22:33
* @param: head
* @ReturnType: linkedList.ListNode
**/
public static ListNode reverseList(ListNode head) {
// 当节点为空,或节点是尾节点时,返回当前节点
if (head == null || head.next == null) {
return head;
}
// 递归寻找尾节点
ListNode newHead = reverseList(head.next);
// 设置尾节点(新节点)的下一个节点为尾节点的上一个节点
head.next.next = head;
// 经过上一步,在尾节点与尾节点的上一个节点间形成死循环,都是对方的下一个节点;
// head.next = null 设置尾节点的上一个节点指向 null,打破死循环,此时尾节点指向尾节点的上一个节点,完成第一步反转;
head.next = null;
// 递归后新节点为之前的尾节点,从尾到头依次反转完成
return newHead;
}
解法2:非递归
非递归方式相较于递归方式略显复杂,用到了三个指针,不断改变指针的指向,一步步推出结果。
推导过程
0. 首先判断头指针 head 是否为 null,或者 head 的下一个节点是否为空;
1. 定义空节点 prev;当前节点 cur 为 head 指向的节点;
此时 prev = null, cur => 1 => 2 => 3 => 4 => 5 => null
2. 此时 cur 为新的链表,也是主要的操作对象,当 cur 不为 null 时进入循环;
3. 截取 cur 第一个节点之后的链表赋予 next,此时 next => 2 => 3 => 4 => 5 => null;
4. 将 prev(null)赋予 cur.next,此时 cur => 1 => null;
5. 将 cur 赋予 pev,此时 pev => 1 => null;
6. 最后将 next 赋予 cur,此时 cur => 2 => 3 => 4 => 5 => null;结束第一次循环;
当进入下一次循环时,next 指向的链表长度不断缩减,cur 指向的链表在循环中反转后赋予 prev,使得 prev 指向的链表作为反转结果。
各个节点指向的链表变化单步调试如下:
初始:
perv = null
cur => 1 => 2 => 3 => 4 => 5 => null第一次循环:
next => 2 => 3 => 4 => 5 => null
cur => 1 => null
pev => 1 => null
cur => 2 => 3 => 4 => 5 => null第二次循环:
next => 3 => 4 => 5 => null
cur => 2 => 1 => null
pev => 2 => 1 => null
cur => 3 => 4 => 5 => null第三次循环:
next => 4 => 5 => null
cur => 3 => 2 => 1 => null
pev => 3 => 2 => 1 => null
cur => 4 => 5 => null第四次循环:
next => 5 => null
cur => 4 => 3 => 2 => 1 => null
pev => 4 => 3 => 2 => 1 => null
cur => 5 => null第五次循环:
next => null
cur => 5 => 4 => 3 => 2 => 1 => null
pev => 5 => 4 => 3 => 2 => 1 => null
cur => null结束循环。输出:pev => 5 => 4 => 3 => 2 => 1 => null
非递归方式题解完整代码如下:
/**
* @Description: 反转链表 非递归方式
* 用三个指针 prev,cur,next ,紧紧相邻,不断前进;
* 每次将 cur.next 指向 prev ,将 prev 指向 cur, cur 指向 next
* @Date: 2019/12/4 22:00
* @param: head
* @ReturnType: linkedList.ListNode
**/
public static ListNode reverseListTwo(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode prev = null;
ListNode cur = head;
while (cur != null) {
// 截取第一个节点之后的链表串赋予 next
ListNode next = cur.next;
// 取 cur 的第一个节点指向 prev,用来拼接反向链表
cur.next = prev;
// 拼接后的结果赋予 prev
prev = cur;
// 将 next 赋予 cur,下次循环截取第一个节点
cur = next;
}
return prev;
}
总结
1. 算法题必须动手实践,不能止步于看懂、听懂;
2. 单步调试非常必要,研究参数值的变化非常必要。一定要调试,一定要调试,一定要调试!!!