四、反转链表
题目:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例1:
示例2:
示例3:
输入:head = [] 输出:[] |
提示:
- 链表中节点的数目范围是 [0, 5000]
- -5000 <= Node.val <= 5000
进阶:
链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
解题思路分析
双指针解法:
定义两个指针cur和pre,用来遍历链表并进行翻转:
- cur指针指向head
- pre指针指向cur的前一个位置,这样方便进行翻转,将cur节点指向它的前一个节点(更改指针方向);那么它的初始值就为null(因为head前面没有节点了,所以是null)
还需要一个临时指针temp来保存cur下一个节点的位置,因为cur指针指向前一个节点pre以后,它的下一个节点已经没有被其他节点所指向了,失去了链接就找不到下一个节点及其后面节点的位置了;
初始时,cur指向head,pre,指向空;然后进入循环进行第一次翻转,翻转后将指针向后移动一位:
然后依次进行翻转:
最后循环结束时,pre就指向了翻转后的新链表的头节点。
伪代码讲解:
//指针初始化,为了能翻转,让cur直接指向它的前一位 cur = head; pre = null; //两个指针同时向后遍历,当cur指向null的时候,pre指向原链表最后一个节点,此时结束循环,不需要再将cur的null指向pre了,所以循环的结束条件是:cur=null while(cur != null) { //定义一个临时指针,用来保存cur下一个节点的位置 temp = cur.next; //将cur节点的next指向pre cur.next = pre; //将两个指针都向后移动一位 //先移动pre;如果先移动cur,那么cur的值就已经改了,pre就无法移动到原来cur的位置了 pre = cur; cur = temp; } //返回新链表的头节点;遍历结束时,pre指向新链表的头节点,cur指向null return pre; |
复杂度分析:
- 时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。
- 空间复杂度:O(1)。
递归解法:
按照双指针的思路来写递归的代码:
伪代码讲解:
//翻转节点指针方向的方法 reverse reverse(cur, pre) { //循环结束条件cur=null ,然后直接返回新链表头指针pre if(cur == null) return pre; //这里是方法开始执行时,需要先定义一个临时指针,用来保存cur下一个节点的位置 temp = cur.next; //然后进行翻转,将cur节点的next指向pre cur.next = pre; //这里要进行移动,开始下一次翻转,直接使用递归的方法 //那么这里就需要pre = cur; cur = temp;,也就是将cur值赋给pre,temp的值赋给cur,对应到reverse(cur, pre)方法中,传入的参数就应该是(temp, cur) return reverse(temp, cur); }
//翻转链表,参数head是要进行翻转操作的链表头指针;最后结果返回新链表的头指针 reverseList(head) { //这里传入上面方法中的指针cur和pre的初始值,分别是head和null //同时,这里直接返回,因为这个方法递归结束以后会返回新链表的头指针pre return reverse(head, null); } |
复杂度分析:
- 时间复杂度: O(n), 要递归处理链表的每个节点
- 空间复杂度: O(n), 递归调用了 n 层栈空间
总结:
要熟练掌握双指针的思路;递归的思路按照双指针思路来写就会非常的清晰,每个参数的赋值也不会混淆。
题解:
自己的解题思路:
先让p和q都指向head,然后将head指向最后一个节点(也就是直接指向翻转后的链表头节点);
p用来遍历原链表,q用来翻转未翻转部分的最后一个节点;
还是太复杂了,没想到p和q可以一起同时向后移动。
而且思路有点绕来绕去,不够简洁。
双指针法:
递归法: