【算法一则】反转链表

题目

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

示例 1:

在这里插入图片描述

输入: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

题解

看到这个题目之后我们第一时间可能想到用ArrayList包过所有的节点,然后通过ArrayList的下标更换来实现链表的反转。代码如下:

public ListNode reverseBetween1(ListNode head, int left, int right) {
    List<ListNode> list = new ArrayList<>();
    // 遍历链表
    while (head != null) {
        list.add(head);
        head = head.next;
    }

    // 交换
    for (int i = left - 1, j = right - 1; i <= j; i++, j--) {
        ListNode temp = list.get(i);
        list.set(i, list.get(j));
        list.set(j, temp);
    }

    // 重新连接
    for (int i = 0; i < list.size() - 1; i++) {
        list.get(i).next = list.get(i + 1);
    }

    // 最后一个节点的next置为null
    list.get(list.size() - 1).next = null;

    return list.get(0);
}

在这里插入图片描述

但这样明眼可见内存和时间都会很不理想,首先遍历了三次链表相当于O(3 * head.length), 空间复杂度也非常高。这个时候我们会想到能不能我的ArrayList只存需要反转那一部分呢,然后再跟不需要反转的链表合并连接下。

空间利用率提升

在这里插入图片描述

public ListNode reverseBetween(ListNode head, int left, int right) {
    ListNode startHeadPre = null; // left 节点的前一个节点
    ListNode endPre = null; // right 节点的后一个节点
    ListNode pre = new ListNode(); // 辅助节点,用于遍历链表

    pre.next = head; // 将辅助节点的下一个节点指向原始链表的头节点
    ListNode realHead = pre; // 记录原始链表的头节点
    List<ListNode> listNodes = new ArrayList<>(); // 用于存储需要反转的节点

    int index = 0; // 当前遍历的节点索引
    while (head != null && head.next != null) {
        if (index == left - 1) {
            startHeadPre = pre; // 记录 left 节点的前一个节点
        }

        if (startHeadPre == null) {
            index ++;
            pre = head;
            head = head.next;
            continue;
        }

        listNodes.add(head); // 将需要反转的节点加入到 listNodes 中

        if (index == right - 1) {
            endPre = head.next; // 记录 right 节点的后一个节点
            break;
        }

        index++;
        pre = head;
        head = head.next;
    }

    if (right - 1 == index) {
        listNodes.add(head); // 如果 right 节点是最后一个节点,则将其加入到 listNodes 中
    }

    ListNode reHead = new ListNode(); // 反转后的链表头节点
    ListNode reHeadTemp = reHead; // 用于遍历反转后的链表

    if (listNodes.size() > 0) {
        reHead.next = listNodes.get(listNodes.size() - 1); // 反转后的链表头节点指向 listNodes 的最后一个节点
        reHeadTemp = reHead.next;
        for (int i = listNodes.size() - 2; i >= 0; i--) {
            reHeadTemp.next = listNodes.get(i); // 逐个将节点加入到反转后的链表中
            reHeadTemp = reHeadTemp.next;
        }
    }

    if (left - 1 == 0) {
        realHead.next = reHead.next; // 如果 left 节点是原始链表的头节点,则更新原始链表的头节点为反转后的链表头节点
    }

    if (startHeadPre != null) {
        startHeadPre.next = reHead.next; // 将 left 节点的前一个节点的 next 指针指向反转后的链表头节点
    }
    reHeadTemp.next = endPre; // 将反转后的链表尾节点的 next 指针指向 right 节点的后一个节点
    return realHead.next; // 返回原始链表的头节点
}

这段代码实现了将链表中从 left 到 right 区间的节点进行反转。它使用了多个辅助节点和变量来记录反转过程中的节点位置。

通过遍历链表,找到 left 节点的前一个节点 startHeadPre 和 right 节点的后一个节点 endPre。同时,将需要反转的节点存储在 listNodes 中。

然后,创建反转后的链表头节点 reHead,并通过遍历 listNodes 将节点逐个加入到反转后的链表中。
最后,根据情况更新原始链表的头节点和连接反转后的链表。
这段代码的时间复杂度为 O(n),其中 n 是链表的长度。然而,它的实现方式相对复杂且效率较低。可以使用更简单和高效的方法来实现链表反转。

在这里插入图片描述
可以看到空间复杂度低了,但是代码异常复杂,很难理解(水平有限😂)。而且时间复杂度和空间复杂度也不是最优,能不能使用一趟扫描完成反转?

进阶

你可以使用一趟扫描完成反转吗?
在这里插入图片描述

public ListNode reverseBetween(ListNode head, int left, int right) {
    ListNode dummy = new ListNode(); // 创建虚拟头节点
    dummy.next = head; // 将虚拟头节点指向原始链表的头节点
    ListNode prev = dummy; // prev 指针用于定位到 left 的前一个节点

    // 移动 prev 到 left 的前一个节点
    for (int i = 0; i < left - 1; i++) {
        prev = prev.next;
    }

    ListNode start = prev.next; // 记录 left 节点
    ListNode end = start; // 记录 right 节点
    ListNode curr = start.next; // 当前节点

    // 反转链表节点
    for (int i = left; i < right; i++) {
        ListNode next = curr.next; // 保存当前节点的下一个节点
        curr.next = prev.next; // 将当前节点的 next 指针指向 prev 的 next 节点
        prev.next = curr; // 将 prev 的 next 指针指向当前节点
        start.next = next; // 将 start 的 next 指针指向 next 节点,保持链表连接
        curr = next; // 更新当前节点为下一个节点
    }

    return dummy.next; // 返回虚拟头节点的下一个节点,即为反转后的链表
}

在这里插入图片描述
这段代码实现了将链表中从 left 到 right 区间的节点进行反转。它使用了一个虚拟头节点 dummy,并通过 prev 指针定位到 left 的前一个节点。

在反转过程中,使用 start 记录 left 节点,end 记录 right 节点,curr 作为当前节点。通过遍历链表,将 curr 节点插入到 prev 节点之后,同时更新 start 节点和 curr 节点的位置。

最后,返回虚拟头节点的下一个节点,即为反转后的链表。

这段代码的时间复杂度为 O(n),其中 n 是链表的长度。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

澄风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值