题目:
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例:
示例一:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例二:
输入:head = [1,2]
输出:[2,1]
示例三:
输入:head = []
输出:[]
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
进阶:
链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
思路
这道题的关键就是如何处理节点的 next 指针。
该问题是一个关于链表问题,先要将完整的问题拆分成子问题,自然想到以链表的节点作为子问题的单元。我们聚焦到链表中的单个节点,发现只要将每个节点的 next 指针指向前一个节点就可以实现链表的反转了。所以处理每一个节点时,需要用到该节点的前一个节点。
迭代实现
迭代的思路比较常规,保存两个指针,一个 cur
指针指向当前遍历的节点,一个 prev
指针指向当前遍历的节点的前一个节点,对链表遍历一遍,将每个节点的 next 指针指向 prev
。
递归实现
递归的思路通常不会第一时间被想到,注意这个方法存在调用栈超过最大限制的风险。思路就是利用函数调用栈先进后出的结构特点,函数调用时栈帧压入栈的顺序,与函数返回时栈帧弹出栈的顺序正好相反。所以如果按照原来的顺序对每个节点依次调用递归函数,那么在函数的返回阶段,相当于按照相反的顺序访问链表,这样每个函数中,递归调用的返回值就正好是当前节点的上一个节点。这样相当于隐式保存了当前节点和上一个节点,但是这样在调用时会在调用栈保存链表中的所有节点,所以内存占用会比较大。
注意
- 递归实现中,注意要保存递归调用阶段的最后一个节点,这个节点是反转后的链表的首节点,也是该问题的返回值。
- 反转前的第一个节点,反转过后,这个节点就变成了最后一个节点,所以需要将这个节点的 next 赋值为 null,否则链表遍历一轮之后链表中会出现环,导致无法跳出循环或者递归过程。
代码
公共部分
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
迭代实现
function reverseList(head: ListNode | null): ListNode | null {
if(!head || head.next === null){
return head;
}
let [prev, cur] = [head, head.next];
head.next = null;
while(cur){
const next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
};
递归实现
function reverseList(head: ListNode | null): ListNode | null {
if(!head) return head;
let newHead = head;
function reverseListCore(head: ListNode | null): ListNode | null {
if (head.next !== null) {
const reversedListTail = reverseListCore(head.next);
reversedListTail.next = head;
} else {
newHead = head; // 保存最后一个节点作为反转后链表的第一个节点
}
return head;
}
reverseListCore(head);
head.next = null; // 防止出现环
return newHead;
};