92. 反转链表 II(Java实现)

力扣:92.反转链表II(中等)
参考:labuladong
 
题目:反转单链表的一部分,就是给你一个索引区间,让你把单链表中这部分元素反转,其他部分不变:

答案:

ListNode reverseBetween(ListNode head, int m, int n) {
    // base case 如果 m == 1,就相当于反转链表开头的 n 个元素
    if (m == 1) { 
        return reverseN(head, n);
    }
    // 前进到反转的起点触发 base case
    head.next = reverseBetween(head.next, m - 1, n - 1);
    return head;
}

ListNode successor = null; // 后驱节点

// 反转以 head 为起点的 n 个节点,返回新的头结点
ListNode reverseN(ListNode head, int n) {
    if (n == 1) { 
        successor = head.next;
        return head;
    }
    ListNode last = reverseN(head.next, n - 1); // 以 head.next 为起点,反转前 n - 1 个节点

    head.next.next = head; // head.next 节点指向 head (此时1,2相互指向)
    head.next = successor; // head 节点指向后驱节点
    return last;
}

赶时间的朋友直接复制走,力扣能够通过,下面开始是解析。
 


通过递归实现,就需要由浅入深,先从反转整个单链表说起。

一、递归反转整个链表
ListNode reverse(ListNode head) {
    if (head.next == null) return head; // 只剩头结点,直接返回
    ListNode last = reverse(head.next); // 将 head.next 进行反转,last 接收反转后的头结点
    head.next.next = head; // head 放到 head.next 的后面
    head.next = null; // head 变成尾结点,末尾 null
    return last; // last 作为头结点返回
}

代码解释:

输入一个节点 head,将「以 head 为起点」的链表反转,并返回反转之后的头结点

比如说我们想反转这个链表:



这个 reverse(head.next) 执行完成后,整个链表就成了这样:(1,3均指向2)

并且根据函数定义,reverse 函数会返回反转之后的头结点,我们用变量 last 接收了。

接下来:head.next(2) 指向 head(1)

head.next(2) 从2改到指向 null

  1. base case:if (head.next == null) return head;

    意思是如果链表只有一个节点的时候反转也是它自己,直接返回即可。

  2. 当链表递归反转之后,新的头结点是 last,而之前的 head 变成了最后一个节点,别忘了链表的末尾要指向 null:

    head.next = null;
     

二、反转前N个节点

比如说对于下图链表,执行 reverseN(head, 3)

ListNode successor = null; // 后驱节点

// 反转以 head 为起点的 n 个节点,返回新的头结点
ListNode reverseN(ListNode head, int n) {
    if (n == 1) { 
        successor = head.next;
        return head;
    }
    ListNode last = reverseN(head.next, n - 1); // 以 head.next 为起点,反转前 n - 1 个节点

    head.next.next = head; // head.next 节点指向 head (此时1,2相互指向)
    head.next = successor; // head 节点指向后驱节点
    return last;
}

举例:

  1. 原链表:1->2->3->4->5->6
  2. 指定 head.next 为起点(2->3…)的反转已经完成,即:last 指向 3->2->4->5->6
  3. head:1->2…(其他节点按last顺序,1,3都指向2),head.next 节点(2)指向 head(1) (此时1,2相互指向)
  4. head 节点(1)指向后驱节点(4->5->6)
  5. 最终:3->2->1->4->5->6

具体的区别:

1、base case 变为 n == 1,反转一个元素,就是它本身,同时要记录后驱节点

2、刚才我们直接把 head.next 设置为 null,因为整个链表反转后原来的 head 变成了整个链表的最后一个节点。但现在 head 节点在递归反转之后不一定是最后一个节点了,所以要记录后驱 successor(第 n + 1 个节点),反转之后将 head 连接上。
 

三、反转链表的一部分
ListNode reverseBetween(ListNode head, int m, int n) {
    // base case 如果 m == 1,就相当于反转链表开头的 n 个元素
    if (m == 1) { 
        return reverseN(head, n);
    }
    // 前进到反转的起点触发 base case
    head.next = reverseBetween(head.next, m - 1, n - 1);
    return head;
}

 

四、最后总结

递归的思想:不要跳进递归,而是利用明确的定义来实现算法逻辑。

处理看起来比较困难的问题,可以尝试化整为零,把一些简单的解法进行修改,解决困难的问题。

值得一提的是,递归操作链表并不高效。和迭代解法相比,虽然时间复杂度都是 O(N),但是迭代解法的空间复杂度是 O(1),而递归解法需要堆栈,空间复杂度是 O(N)。所以递归操作链表可以作为对递归算法的练习或者拿去和小伙伴装逼,但是考虑效率的话还是使用迭代算法更好。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值