给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
一、非递归反转链表
var reverseList = function(head) {
// 注意:链表为空和只有一个元素的情况
if(!head || !head.next){
return head;
}
// 采用头插法反转链表
let h = null, p = null; //h指向反转链表的表头,初始为null
while(head){
p = head.next; //暂存head.next
head.next = h; //head插入到表头
h = head; //h指向表头
head = p;
}
return h;
};
二、递归反转链表
// 递归方式
var reverseList = function(head) {
if(!head || !head.next){
return head;
}
let reverse = reverseList(head.next); //反转头节点后面的链表
head.next.next = head; //将头节点添加到链表尾部
head.next = null;
return reverse;
};
三、反转链表的一部分
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
注意:left和right是从1开始计数的
解决方案:建议方法二
方法一:
反转 left
到 right
部分以后,再拼接起来。我们还需要记录 left
的前一个节点,和 right
的后一个节点。如图所示:
算法步骤:
- 将待反转的区域反转;
- 把 pre 的 next 指针指向反转以后的链表头节点,把反转以后的链表的尾节点的 next 指针指向 succ。
var reverseBetween = function(head, left, right) {
// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
const dummyNode = new ListNode(-1);
dummyNode.next = head;
let pre = dummyNode;
// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
// 建议写在 for 循环里,语义清晰
for (let i = 0; i < left - 1; i++) {
pre = pre.next;
}
// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
let rightNode = pre;
for (let i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 第 3 步:切断出一个子链表(截取链表)
let leftNode = pre.next;
let curr = rightNode.next;
// 注意:切断链接
pre.next = null;
rightNode.next = null;
// 第 4 步:同第 206 题,反转链表的子区间
reverseLinkedList(leftNode);
// 第 5 步:接回到原来的链表中
pre.next = rightNode;
leftNode.next = curr;
return dummyNode.next;
};
const reverseLinkedList = (head) => {
let pre = null;
let cur = head;
while (cur) {
const next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/fan-zhuan-lian-biao-ii-by-leetcode-solut-teyq/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
方法一的缺点是:如果 left 和 right 的区域很大,恰好是链表的头节点和尾节点时,找到 left 和 right 需要遍历一次,反转它们之间的链表还需要遍历一次,虽然总的时间复杂度为 O(N),但遍历了链表 2次。
方法二:一次遍历(头插法)
整体思想是:在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。下面的图展示了整个流程。
var reverseBetween = function(head, left, right) {
if(!head || !head.next || left==right){
return head;
}
// 1. 先找到left位置的前一个结点h
let h = new ListNode(-1);
h.next = head;
let pre = h;
for(let i=0; i<left-1; i++){
pre = pre.next;
}
// 2. 从left位置开始反转链表(不需要反转最左侧的结点,只需将其它结点插入到left前即可)
let cur = pre.next; //cur始终指向反转后的最后一个结点(即反转前的第一个结点),保存反转后部分链表的尾指针的指向
for(let i=0; i<right-left; i++){
const p = cur.next;
cur.next = p.next; // 先将cur的下一个结点删除
p.next = pre.next; //将cur的下一个结点插到头部
pre.next = p;
}
return h.next;
};
复杂度分析:
- 时间复杂度:O(N),其中 N是链表总节点数。最多只遍历了链表一次,就完成了反转。
- 空间复杂度:O(1)。只使用到常数个变量。