1.题目
原题链接:LeetCode 206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
2.分析与题解
本题是最高频的面试题之一。
思路一:迭代
不妨设链表为1 -> 2 -> 3 -> null,最终目标是:把它反转为null <- 1 <- 2 <- 3,并返回头结点3。
迭代,意思就是遍历每一个节点,对每个节点都做相同的一系列操作,最终使链表反转。
那么,在迭代过程中,究竟要如何对每一个节点进行操作呢?
- 肯定要改变每个节点的next指针指向,从原来的指向下一个节点,到指向它的上一个节点(如果是第一个节点,就指向null)。
- 但是,对于当前节点的下一个节点,我们也要进行同样的操作,现在改变指向了,我们就找不到下一个节点的位置了,所以还要事先把下一个节点的位置存储起来。
- 这里的链表是单链表,不自带前驱节点,所以为了找到当前节点的的上一个节点,我们还要存储上一个节点的位置。
- 改变完next指针的指向后,我们就要移动到下一个节点,对其进行相同的操作了。注意,前驱节点和当前节点都要向后移动一个节点。而且必须先移动前驱节点,再移动当前节点,否则当前节点的位置会丢失。
//Java
//思路一:迭代
class Solution {
public ListNode reverseList(ListNode head) {
//迭代
ListNode prev = null, curr = head, next;
while(curr!=null){
next = curr.next;//存储下一个节点的位置
curr.next = prev;//改变指针方向
prev = curr;//向后移动前驱节点
curr = next;//向后移动当前节点
}
return prev;
}
}
时间复杂度:O(N)
空间复杂度:O(1)
思路二:递归
先看代码:
//Java
//思路二:递归
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next==null) return head;//base case
ListNode newHead = reverseList(head.next);//调用自身
head.next.next = head;//修改2的指向关系
head.next = null;//修改1的指向关系
return newHead;
}
}
递归,字面意思就是先递后归。
“递”,就是方法内部调用自身,一层层压栈。比如上面代码中,reverseList(ListNode head)方法内部调用了自身:
ListNode newHead = reverseList(head.next);
“归”,就是终于在某一次调用中,符合了方法最开头的条件:
if(head == null || head.next==null) return head;
根据题解代码可知,这次调用还没执行到调用自身的那一行,就返回了。也就是没有它再次调用自身,而是正常出栈了。这样,就引起了连锁反应,之前一层层调用自身的方法依次出栈,这便是“归”。
通过这个例子可知,写递归,要在代码的最开头写一个条件的判断,判断是不是结束“递”,开始“归”。这个条件就是所谓的“base case”。
然而,我们在思考递归的过程中,却尽量不要像实际上计算机的处理方式那样,一层一层地压栈,又一层一层地出栈,非得深入到递归关系内部。下面说一下什么才是正确的思考方式:
以示例1为例,刚输入时,链表是1 -> 2 -> 3 -> 4 -> 5 ->null,然后执行这一句调用自身:
ListNode newHead = reverseList(head.next);
reverseList(head.next)的含义是什么?反转以head.next为首节点的链表,并返回最新的头节点。以head.next为首节点的链表,也就是2 -> 3 -> 4 -> 5 ->null,反转之后就是null<- 2 <- 3 <- 4 <- 5 ,并返回新的首节点5。那么你就当作已经反转完成,不要跳进一层一层的调用里,那么现在的链表就是:
null<- 2 <- 3<- 4 <- 5
↗
1
之后那两行代码就是修改一下1和2这两个节点的指向关系,最终的链表是这样的:
2 <- 3 <- 4 <- 5
↙
null <- 1
也就是已经反转成功了,新的首节点就是刚才得到的那个newHead,返回即可。
时间复杂度:O(N)
空间复杂度:O(1)
递归方法和迭代方法的时间复杂度一样。但递归方法需要占用堆栈,所以空间复杂度是O(N)。所以相比递归操作链表来讲,迭代效率更高一些。
3.总结
本题到此结束,可以自己点击这里去LeetCode官网试一试。反转链表是面试中最高频的题目之一,迭代和递归的方法都应该熟练掌握,都比较简单。
我接下来马上会更新本题的进阶版:LeetCode 92. 反转链表 II 以及 LeetCode 25. K 个一组翻转链表,欢迎点赞、收藏、分享~