给你单链表的头节点
head,请你反转链表,并返回反转后的链表。
解法1 暴力解法
思路
因为是单向链表,所以只能一直往前走。
这样可以遍历整个链表缓存下来,然后再从后面开始遍历修改 next 指向。
代码
function reverseList(head: ListNode | null): ListNode | null {
if (!head) {
return null;
}
let cur = head;
const nodeList = []; // 缓存链表
while (cur) {
nodeList.push(cur);
cur = cur.next;
}
const n = nodeList.length - 1;
for (let i = n; i >= 0; i --) {
nodeList[i].next = i === 0 ? null : nodeList[i - 1];
}
return nodeList[n];
};
时空复杂度分析
时间复杂度:一层循环。虽然遍历了两遍,但依然是 O(n)
空间复杂度:使用了数组额外缓存整个链表所以是 O(n)
解法2 原地修改指针
思路
我们可不可以在一次遍历的过程中就修改指针呢?
首先如果修改当前 node 的 next 指向,那么肯定遍历不到 next 节点,所以在修改当前 node 的指针时,我们需要将 next 节点给保存下来,然后再将当前 node 指向之前的 next 节点。
其次修改完的链表最后应该是 null 节点。 最后就是修改指针的顺序很重要,需要先保存 cur.next 节点,然后就可以去修改 cur 的 next,指向上一次个节点 pre。然后移动 cur 和 pre 指针。
代码
function reverseList(head: ListNode | null): ListNode | null {
if (!head) {
return null;
}
let cur = head;
let pre = null;
while (cur) {
let next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
};
时空复杂度
时间复杂度: 一次遍历 O(n)
空间复杂度:仅使用2个变量 O(1)
解法3 递归
思路
递归先思考终止条件:当前节点为空或没有下一个节点。
按照上图,终止条件为当前 head 为4,而递归回来的节点是 5,这时候需要修改 4 -> 5 为 5 -> 4。
所以 head.next(这是节点5).next 修改为 head 自身,然后切断当前 4 -> 5 的指向。
最后返回递归回来的节点。
代码
function reverseList(head: ListNode | null): ListNode | null {
if (!head || !head.next) {
return head;
}
const newHead = reverseList(head.next);
head.next.next = head; // 将当前节点的下一个节点指向当前节点
head.next = null; // 断开原有指针
return newHead;
};
时空复杂度
时间复杂度:O(n)
空间复杂度:O(n),主要是递归调用栈空间消耗