[路飞]_每天刷leetcode_49(反转链表 II)

492 阅读4分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

反转链表 II(Reverse Linked List II)

LeetCode传送门92. 反转链表 II

题目

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

Given the head of a singly linked list and two integers left and right where left <= right, reverse the nodes of the list from position left to position right, and return the reversed list.

Example:


Input: head = [1,2,3,4,5], left = 2, right = 4
Output: [1,4,3,2,5]



Input: head = [5], left = 1, right = 1
Output: [5]

Constraints:

  • The number of nodes in the list is n.
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

思考线


解题思路

我们要如何解答这道题呢?

首先我们要知道如何从头到尾如何去反转一个链表,如果不太清楚如何做的同学可以参考这个我之前录的B站视频链接地址在这里

知道了如何去从头到尾反转一个链表,在这里我们假设left节点为头部,right节点为尾部,我们可以反转从leftright之间的链表,在这里我们假设翻转后的left~right链表为reversed

翻转完成leftright之间的链表以后,留给我们的问题就是如何让翻转后的链表链reversed接好未翻转的头部未翻转的尾部

我们可以分情况考虑:

我们先看头部的变化:

  • left !==1则说明有未反转的头部,我们记录下头部的位置preLeftNode,在翻转完成后,把preLeftNode指向翻转后的头部,而此时整个链表的头部没有发生变化仍然是head
  • left ===1 则说明没有未反转的头部,则此时的头部发生了变化,头部为反转链表reversed的头部。 我们再看看尾部会发生的变化:
  • right === 链表的尾部, 则说明,反转后的链表reversed尾部就是我们最终链表的尾部。在这里我们把翻转后的链表reversed的尾部leftNodenext指针指向 null ,否则链表会出现环。
  • right !== 链表的尾部, 则说明,反转后的链表reversed尾部需要链接到原始链表rightNode的下一个节点。在这里我们把翻转后的链表reversed的尾部leftNodenext指针指向 原始链表的下一个节点即可。

至此,我们完成了链表翻转的逻辑,最后 我们把新的头部返回即可。

代码如下

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     val: number
 *     next: ListNode | null
 *     constructor(val?: number, next?: ListNode | null) {
 *         this.val = (val===undefined ? 0 : val)
 *         this.next = (next===undefined ? null : next)
 *     }
 * }
 */

function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
    let list = head;
    let i = 0
    let pre: ListNode | null; // 翻转过程中的前一个
    let next: ListNode | null; // 翻转过程中的后一个
    let preLeftNode: ListNode | null; // 原始链表中left的前一个节点
    let leftNode: ListNode | null; // left对应的节点
    let rightNode: ListNode | null; // right对应的节点
    let rNext: ListNode | null; // 原始链表中right对应的下一个节点
    while (list) {
        i++;
        if (i === left - 1) preLeftNode = list;
        if (i === left) {
            pre = list;
            leftNode = list;
            next = pre.next;
        }
        if (i === right) {
            rightNode = list;
            if (list.next) rNext = list.next;
            break;
        }
        list = list.next;
    }

	// 从 left 到right 来反转链表
    while (next && pre !== rightNode) {
        const temp = next.next;
        next.next = pre;
        pre = next;
        next = temp;
    }
    let newHead: ListNode | null;;

	// 处理头部逻辑
    if (preLeftNode) {
        newHead = head;
        preLeftNode.next = pre;

    } else {
        newHead = pre;
    }
	// 处理尾部逻辑
    if (rNext) {
        leftNode.next = rNext;
    }else {
        leftNode.next = null;
    }


    return newHead;

};

以上是我的第一版代码,那么我们怎么对这个思路进行代码优化呢?

为了减少头部的判断我们可以给链表加上一个虚拟头结点dummyHead这样一来,我们的新头部节点永远指向dummyHead.next.

而我们尾部的指向呢? 通过观察我们可以发现,rNext无论是指向null还是真的有节点,我们都只要把翻转后的节点尾部指向rNext即可,这样我们可以得到稍微优化后的代码,如下

function reverseBetween(head: ListNode | null, left: number, right: number): ListNode | null {
    const dummyHead = new ListNode(0);
    dummyHead.next = head;
    let i = 0
    let pre: ListNode | null; // 翻转过程中的前一个
    let next: ListNode | null; // 翻转过程中的后一个
    let preLeftNode: ListNode | null; // 原始链表中left的前一个节点
    let leftNode: ListNode | null; // left对应的节点
    let rightNode: ListNode | null; // right对应的节点
    let rNext: ListNode | null = null; // 原始链表中right对应的下一个节点
    if (left === 1) preLeftNode = dummyHead;
    while (head) {
        i++;
        if (i === left - 1) preLeftNode = head;
        if (i === left) {
            pre = head;
            leftNode = head;
            next = pre.next;
        }
        if (i === right) {
            rightNode = head;
            if (head.next) rNext = head.next;
            break;
        }
        head = head.next;
    }

    // 从 left 到right 来反转链表
    while (next && pre !== rightNode) {
        const temp = next.next;
        next.next = pre;
        pre = next;
        next = temp;
    }
    preLeftNode.next = pre;
    leftNode.next = rNext;


    return dummyHead.next;

};

时间复杂度分析

O(n): 其中n为链表的总节点,最坏的情况下,需要遍历整个链表。

这就是我对本题的解法,如果有疑问或者更好的解答方式,欢迎留言互动。