【前端面试常见算法题系列】92. 反转链表 II

113 阅读2分钟

“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”

一、题目描述

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

示例 1: image.png

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1
输出:[5]

提示:

  • 链表中节点数目为 n
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

进阶: 你可以使用一趟扫描完成反转吗?

二、思路分析

之前写过一篇反转链表的简单题【前端面试常见算法题系列】206. 反转链表,如果你看了这篇题解,相信已经能够理解反转链表的具体流程了,那么再来做这道题会很简单,下面废话不多说,我们进入正题。

这道题要求返回只反转部分节点的链表,函数签名如下:

ListNode* reverseBetween(ListNode* head, int left, int right) {}

left 为反转的起点,right 为反转的终点。

思路其实很简单: 不需要反转的地方正常遍历,走到 left 的位置再将需要反转的部分传参给反转函数(并不是真的传递部分链表,而是传递部分链表的起止位置以及反转部分的第一个节点),而这里的反转函数几乎跟上面那篇题解的反转思路一致,但有一个点需要特别注意:反转后最后的指针不是指向 nullptr 了,而是指向需要反转的部分链表的后一个元素。

  1. 我们比较常用的链表遍历方式是迭代遍历,那如果是递归遍历呢,再加个难度:遍历到指定位置进行反转。(于是我想啊想啊,终于知道了)不就是递归吗,将当前链表节点的下一个节点传递给递归函数,那么就这个递归函数而言,里面的 head 其实不再是刚开始的那个节点了,而是第二个节点,再递归,递归函数里的 head 就变成第三个节点了,以此类推... 这样就实现了递归遍历。

  2. 很好,递归遍历的问题解决了,那么又有一个新问题了,因为是原链表修改,那最后怎么返回头结点呢?其实这个问题不用担心,我们并没有 head = head -> next ,因此头结点还是那个头结点。

  3. 好极了,返回了头结点,又进行反转了,接下来就剩下最后的链表指向了。假设链表需要反转的部分是中间部位,我们将链表的前面部分设为 1 、反转部分设为 2 、后面的部分设置为 3 (这里是为了方便讲解设立的理想情况)那么只要 1 部分的最后一个节点指向 2 部分的最后一个节点、 2 部分的第一个节点指向 3 部分的第一个节点,那么就大功告成了。(有没有什么思路呢)

  • 递归遍历的时候会一直走到反转函数,而反转函数又会继续递归,那么只要在反转的时候保存 2 部分的第一个节点,等到反转完毕就会 return ,也就是返回了反转后的头结点,因此在递归遍历时 head -> next 指向递归函数的返回值皆可;
  • 接下来将 2 部分反转后的最后一个节点指向 3 部分的第一个节点就好了,只要在遍历到 2 部分的最后一个节点时,记录它的下一个节点也就是 3 部分的第一个节点(假设为 sign),就可以操作了,其实也只是在上面那篇题解的反转函数中 ans = sign 即可。

至此,这道题就到这里了,如果我讲的不明白,可以结合代码和上面的思路,自己按照示例 debug 一下应该就可以了,如果没能帮大家搞清楚,还请见谅!

三、AC 代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

class Solution {
public:
    ListNode* successor = nullptr;

    ListNode* reverse(ListNode* head, int n) {
        if (n == 1) {
            successor = head -> next;
            return head;
        }

        ListNode* last = reverse(head -> next, n - 1);

        head -> next -> next = head;
        head -> next = successor;
        return last;
    }

    ListNode* reverseBetween(ListNode* head, int left, int right) {
        if (left == 1) {
            return reverse(head, right);
        }

        head -> next = reverseBetween(head -> next, left - 1, right - 1);
        return head;
    }
};