题目描述
解法一:借助外部空间
先申请一个集合,比如ArrayList,通过对链表的遍历将其节点进行存储记录,之后再对集合进行倒序循环,同时指定其指针域为前一个节点,这样就相当于重构链表。当然,利用其他集合,比如栈也是可以的,只不过使用到的方法不同。
public ListNode reverseList(ListNode head) {
if(head == null)
return null;
List<ListNode> res = new ArrayList<>();
ListNode cur = head;
while(cur != null){
res.add(cur);
cur = cur.next;
}
head = res.get(res.size() - 1);
for(int i = res.size() - 1;i >= 0;i--){
if(i == 0){
res.get(i).next = null;
break;
}
res.get(i).next = res.get(i-1);
}
return head;
}
小结:
该解法难免显得简单了点,并且你在面试中这么做的话,面试官 100% 会追问是否有更优的方式,比如不用外部空间。
解法二:双指针迭代
双指针技巧是解决链表问题经常用到的方法,它可以在给定的链表上原地解决问题,也是我优先考虑到的方法。
首先,通过对反转结果分析,将节点指针方向进行转变就可以解决问题。拆解问题的话,就是需要两个指针指向两个相邻的节点,一个作前指针,一个作后指针,将后指针的指针域指向前指针,接着将两个指针均后移一位,循环上述操作,直到不满足循环条件。
步骤如下:
1. 指针初始化:pre = null,cur = head;
2. 记录当前节点的下一个节点
tmp = cur.next;
然后将当前节点指向pre
cur.next = pre;
pre和cur节点都前进一位
pre = cur;
cur = tmp;
3. 当cur != null 时,一直循环操作2
4. 最终的pre即为所求结果,返回即可。
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode prev = null;
while(cur != null){
ListNode temp = cur.next;
cur.next = prev;
prev = cur;
cur = temp;
}
return prev;
}
解法三:递归
下面介绍我一开始所想到的递归思路,既然解法二在原地实现了解题,那么稍加改造是否就能改写成递归呢?
我通过函数传递两个参数即两个节点,让其做指定操作,操作完成后再将之后符合条件的节点传进参数,进行下一次操作,直至递归结束。
class Solution {
public ListNode reverseList(ListNode head) {
return reverse(null,head);
}
public ListNode reverse(ListNode pre,ListNode cur){
if(cur == null)
return pre;
ListNode temp = cur.next;
cur.next = pre;
pre = cur;
return reverse(pre,temp);
}
}
小结:
这种递归像是对解法二循环的改造,虽然比较好理解,但是还是感觉不是纯正的递归解法(可能是,我所能想出来的我就觉得比较 low 吧,哈哈哈哈~)。
解法四:递归
接下来介绍一种比较难理解的递归解法,步骤如下:
首先确定递归终止条件:当前节点或者下一个节点 == null 即 head == null || head.next == null,此时返回头节点head;
接下来实现递归体:改变节点的指向,也就是 head 的下一个节点指向 head 即 head.next.next = head;
最后确定返回值:每层递归函数都返回cur,也就是最后一个节点,也可以理解为已经反转好的那一部分链表。
public ListNode reverseList(ListNode head) {
if(head==null || head.next==null) {
return head;
}
ListNode cur = reverseList(head.next);
head.next.next = head;
head.next = null;
return cur;
}
小结:
搞清楚这个递归代码后,我才恍然大悟,这不就是二叉树的后序遍历嘛(对节点操作的代码置于遍历代码后)。而这段递归代码就是先对链表进行遍历,遍历到最后一个节点时开始改变节点的指向,并返回已反转成功的部分链表的头节点。
那么解法三的递归不就是前序遍历嘛,先对已遍历过的节点改变其节点的指向,接下来进行下一次的遍历,直到递归终止。
反观该递归过程其实并不是很复杂,只不过是一时昏头,突然忘了递归函数的执行过程,还有对这句代码 ListNode cur = reverseList(head.next); 理解错误。