2、链表反转之固定区间反转

    在上章我介绍了第一种即完全反转的算法,在本章则会重点讲解第二种即指定区间反转的算法(注:我不仅仅只是给出时间复杂度和空间复杂度最低的算法,有一些容易理解的算法我也会给出,可以帮助大家从不同的视角理解问题,但是最优解我一般会给出标注、具体示例以及对应的Java代码)

2.1 直接迭代反转(本方法很容易理解,但在具体处理上可能有很多小细节)

  1. 本章是对于指定区间进行反转,如果要联系上一章的知识,那么我们可以将该局部部分先断开当作完整链表来进行反转
  2. 再将已经反转好的局部链表与断开的节点建立连接,再重构链表

本方法较容易理解,但不太推荐,因为要断开再链接的话可能需要多个结点的辅助。

  • 时间复杂度: O(n),其中 n 是链表节点的数量。
  • 空间复杂度: O(1),因为只使用了几个额外的变量。

2.2 头插迭代法(与法一类似,但是并没有断开、连接,而是使用头插法的思想直接就地逆置,本人认为最优解)

  • 假设链表为 1 -> 2 -> 3 -> 4 -> 5, m = 2, n = 4
  • dummy 节点指向 head,用于处理 m = 1 的特殊情况。
  1. 初始化:
    dummy -> 1 -> 2 -> 3 -> 4 -> 5

    prev

  2. prev 定位到第 m-1 个节点(节点1)
    dummy -> 1 -> 2 -> 3 -> 4 -> 5
    prev

  3. 开始反转:

    • 第一次反转后:
      dummy -> 1 -> 3 -> 2 -> 4 -> 5
      prev
    • 第二次反转后:
      dummy -> 1 -> 4 -> 3 -> 2 -> 5
      prev

反转完成。

对于该方法,有一个很好的记忆方式,比如对于上述例子,我们要把2、3、4进行反转,那么每次迭代的时候都把2后面的结点进行头插操作,这样的话就变成了1、3、2、4、5,下一次迭代,则是再把2后面的结点进行头插操作,变成1、4、3、2、5,依次进行迭代,直到全部完成。

Java代码示例:

public ListNode reverseBetween(ListNode head, int m, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode prev = dummy;
// 定位到第m-1个节点
for (int i = 0; i < m - 1; i++) {
    prev = prev.next;
}

ListNode current = prev.next;
ListNode next;

// 反转第m到第n个节点
for (int i = 0; i < n - m; i++) {
    next = current.next;
    current.next = next.next;
    next.next = prev.next;
    prev.next = next;
}

return dummy.next;
}
  • 时间复杂度: O(n),其中 n 是链表节点的数量。
  • 空间复杂度: O(1),因为只使用了几个额外的变量。

2.3 递归求解

本次递归求解配合Java具体代码来进行分析;

Java代码如下:

public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
    }
}

public class Main {
    public static ListNode reverseBetween(ListNode head, int m, int n) {
        if (m == 1) {
            return reverseN(head, n);
        }
        head.next = reverseBetween(head.next, m - 1, n - 1);
        return head;
    }
static ListNode successor = null; // 后驱节点

public static ListNode reverseN(ListNode head, int n) {
    if (n == 1) {
        successor = head.next;
        return head;
    }
    ListNode last = reverseN(head.next, n - 1);
    head.next.next = head;
    head.next = successor;
    return last;
}

public static void main(String[] args) {
    // 测试代码
}
    
}

假设链表是 1 -> 2 -> 3 -> 4 -> 5m = 2, n = 4

Step 1: 我们首先减小问题规模,即我们先处理 reverseBetween(head.next, m - 1, n - 1)。对于当前的 head,它成为了较小问题里的 m = 1, n = 3

1 -> 2 -> 3 -> 4 -> 5
head

Step 2: 在较小的问题中,因为 m = 1,我们会调用 reverseN 函数。开始局部反转 2 -> 3 -> 4

1 -> 2 -> 3 -> 4 -> 5
    head
     
reverseN(2, 3)
  • n = 1,我们设置 successor = 4->next(即5)

Step 3: 当递归回溯,2 -> 3 -> 4 反转为 4 -> 3 -> 2

1 -> 4 -> 3 -> 2 -> 5

Step 4: 在回溯过程中,逐个设置正确的 next 指针,即 2->next = 5,这是由 successor 保存的。

1 -> 4 -> 3 -> 2 -> 5

Step 5: 最后,返回新的局部反转后的链表头,即节点 4

反转完成。

  • 时间复杂度: 因为我们只对链表进行了一次遍历,所以时间复杂度为 O(n)。
  • 空间复杂度: 递归调用会使用堆栈空间,最坏情况下堆栈深度为 O(n)。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值