题目描述
解题思路
迭代法
迭代法核心就是遍历链表,然后将指针反转即可。主意更改当前节点指针的时候,需要将它的下一个节点的地址先保存起来,以免丢失。
-
初始化三个变量,
cur
当前节点, 当前节点的前一个节点,prev
,next
当前节点的下一个节点let cur = head, prev = null, next;
-
遍历链表,直到尾节点
null
,改变指针指向,注意保存next
的指针,并开始下一轮循环, 最后返回结果。while(cur){ // 保存 next 指针 next = cur.next; // 改变指针 cur.next = prev; // 开始下一轮 prev = cur; cur = next; }
完整代码
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
if(!head) return head;
let prev = null, cur = head,next;
while(cur){
next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
};
复杂度分析
时间复杂度: O(n) 需要遍历链表 n 个元素
空间复杂度: O(1) 只有三个临时变量 prev
cur
next
需要空间,为常数大小的额外空间。
递归法
我个人理解递归法的方法就是画树。递归的过程和栈的入栈和出栈一样,通过递归函数一层一层入栈,然后出栈的时候计算(或者执行某些代码),然后将结果代入下一步,直到遇到终止条件为止,最后将结果返回。
为了讲解简单,我们拿翻转 [1, 2, 3]
举例,最后结果是 [3, 2, 1]
, 当然,链表最终都是指向 null
的。为了直观,我写成 1 -> 2 -> 3 -> null
。
第一步:画树
我们知道,要翻转 1 -> 2 -> 3 -> null
,可以像二叉树的形式拆解。
-
(拆解1)可以先分解成
1 ->
和2 -> 3 -> null
,理解为要翻转整个链表,需要先翻转2 -> 3 -> null
-
(拆解2) 再将
2 -> 3 -> null
拆解成2 ->
和3 -> null
,理解为继而需要翻转3 -> null
-
(拆解3)再将
3 -> null
拆解成3 ->
和null
由此,我们只需要将拆解的步骤从后向前改变指针,并将新的链表的头指针返回即可。
第二步: 考虑终止条件
如果 head
是 null
或者 head.next
是 null
, 我们直接将 head
返回即可。 3 -> null
翻转之后还是 3 -> null
,也是 拆解1 的解。
if(!head || !head.next) return head;
第三步: 递推
如果 head.next
不为 null
的话,就将后续节点反转。
let newHead = reverseList(head.next);
第三步: 改变指针
如何翻转后续节点呢?改变指针指向即可。2 ->
和 3 -> null
怎么翻转,即拆解2。此时,head
即 2 ->
, head.next
即 3
。
-
此时,
3
的指针还指向null
:3 -> null
, 我们让它指向2
:head.next.next = head
。 -
再将
2
的指针指向null
即可:head.next = null
。如果它不是原链表的头节点,这个值会被后面的值覆盖掉,直到遇到头节点为止。
经历过以上步骤之后,现在 2 ->
和 3 -> null
已经翻转成了 3 -> 2 -> null
接下来,1 ->
和 2 -> 3 -> null
的翻转就是重复上述的 1) 2) 步骤。此时,2 -> 3 -> null
已经翻转成 3 -> 2 -> null
,所以直接翻转 1 ->
和 2 -> 3 -> null
。 此时,head
即 1 ->
, head.next
即 2
。
head.next.next = head;
head.next = null;
最终翻转结果为 null <- 1 <- 2 <- 3
, 我们倒过来看就是 3 -> 2 -> 1 -> null
,即最终的结果。记得最后要将结果返回。
完整代码
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
if(!head || !head.next) return head;
let newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead
};
复杂度分析
时间复杂度: O(n) 这里链表有 n 个,每个链表都需要遍历一遍。
空间复杂度: O(n) 递归深度为 n, 可以理解为需要的栈的空间为 O(n)
总结
反转链表可以使用迭代法和递归法。
迭代法需要三个临时变量(prev
、cur
、 next
),修改指针的时候,需要保存当前节点的下一个指针 next = cur.next
,以免后续节点丢失,然后递进节点 prev = cur; cur = next
,最后返回结果即可。
递归需要拆解,想清楚拆解的步骤即可。画树是为了理解,然后将拆解的步骤一步一步往前推即可。