35、反转链表 --- 字节

给你单链表的头节点 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开始计数的

解决方案:建议方法二

解答参考:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/fan-zhuan-lian-biao-ii-by-leetcode-solut-teyq/

 方法一:

反转 left 到 right 部分以后,再拼接起来。我们还需要记录 left 的前一个节点,和 right 的后一个节点。如图所示:

image.png

算法步骤:

  1. 将待反转的区域反转;
  2. 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次。

 

方法二:一次遍历(头插法)

整体思想是:在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。下面的图展示了整个流程。

image.png

image.png

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)。只使用到常数个变量。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值