注:本文的代码实现使用的是 JS(JavaScript),为前端中想使用JS练习算法和数据结构的小伙伴提供解题思路。
描述
给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。
示例:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
输入:head = [1,2]
输出:[2,1]
输入:head = []
输出:[]
提示:
- 链表中节点的数目范围是 [0, 5000]
- -5000 <= Node.val <= 5000
解题思路
修改数值法
创建一个数组vals
,用于存放链表中节点的值。遍历链表,把每一个节点的值依次添加到数组中,通过push
方法即可。然后再次遍历链表,不断把vals
中最后一个元素pop
出来并赋值给当前节点。也就是说,这里的vals
可以看作是一个栈,由于入栈的顺序和出栈的顺序正好是相反的,因此每个元素出栈的时候依次赋值给正向遍历的链表,就可以实现反转链表的目的。这种方法的时间复杂度和空间复杂度为
O
(
n
)
O(n)
O(n),但是相当简单。
var reverseList = function(head) {
let vals = []
let p = head
while(p !== null){
vals.push(p.val)
p = p.next
}
p = head
while(p !== null){
p.val = vals.pop()
p = p.next
}
return head
};
头插法
这个比较好理解了,把原始链表的头节点不断弹出,放到另一个链表的尾部。这种思想跟第一种解法很类似。只需要用一个指针一直指向新的链表的头节点即可。由于只用了三个新的变量,因此空间复杂度为 O ( 1 ) O(1) O(1)。
var reverseList = function(head) {
// 定义新链表的头指针
let newHead = null
// p一直指向原始链表的即将被弹出的首节点
let p = head
while(p !== null){
// 保存原始链表弹出第一个节点后的首节点
const temp = p.next
p.next = newHead
newHead = p
p = temp
}
return newHead
};
递归法
- 定义一个函数
reserveNode
,每次需要传的参数为当前节点cur
和它的上一个节点pre
; - 先在函数中不断递归调用自身,传入的参数分别为
cur.next
和cur
,就是当前节点的下一个节点和当前节点。当然,我们并不能保证cur
是否为空(链表的最后一个节点的next
是null
)。因此,需要判断cur
是否为空。
var reserveNode = function(cur, pre){
if(cur === null) return
console.log(cur,pre)
reserveNode(cur.next, cur)
}
// 题目中所给的主函数
var reverseList = function(head) {
//因为head的上一个节点并不存在,所以第二个参数传为null
return reserveNode(head, null)
};
执行上面的代码,我们可以看到控制台会输出
[1,2,3,4,5] null
[2,3,4,5] [1,2,3,4,5]
[3,4,5] [2,3,4,5]
[4,5] [3,4,5]
[5] [4,5]
其实等价于(每个中括号代表一个链表节点)
[1] null
[2] [1]
[3] [2]
[4] [3]
[5] [4]
也就是说,我们成功得到了每个节点和它上一个节点的组合。那么接下来我们需要把每个组合依次链接。如[5].next = [4] [4].next = [3] ...
以此类推。只需要将cur.next = pre
便可以完成。emmm,要是还不明白,自己用一个实例过一遍就能通啦。由于使用递归函数,所以空间复杂度依旧是
O
(
n
)
O(n)
O(n)
var reverseList = function(head) {
var reserveNode = function(cur, pre){
// cur 指向的是[5]的时候,返回[5]
if(cur === null) return pre
let temp = reserveNode(cur.next, cur)
// 将当前节点的 next 指向它原来的上一个节点
cur.next = pre
// 返回当前节点的 next 指向的节点
return temp
}
return reserveNode(head, null)
};