利用编程思维做题之链表内指定区间反转

牛客网题目

 1. 理解问题

        给定一个单链表和两个整数 mn,要求反转链表中从位置 m 到位置 n 的节点,最后返回反转后的链表头节点。

示例:

  • 输入:链表 1 -> 2 -> 3 -> 4 -> 5 -> NULLm = 2n = 4

  • 输出:1 -> 4 -> 3 -> 2 -> 5 -> NULL

关键点:

  • 原地反转:要求空间复杂度为 O(1),可以有常量级别的中间值保存操作,。

  • 边界条件:需要考虑 m 为 1(即从链表头开始反转)以及 n 为链表长度的情况。

  • 指针处理:需要正确处理反转部分前后的节点连接。

2. 输入输出

  • 输入:

    • head:链表的头节点。

    • mn:指定反转的起始和结束位置,满足 1 ≤ m ≤ n ≤ 链表长度

  • 输出:

    • 反转指定区间后的链表头节点。

3. 链表结构

        链表节点的定义如下:

struct ListNode {
    int val;                // 节点的值
    struct ListNode *next;  // 指向下一个节点的指针
};

4. 制定策略

        为实现指定区间的反转,需要以下步骤:

  1. 定位到反转起始位置的前一个节点:

    • 使用一个辅助的 dummy 节点指向链表头,方便处理 m = 1 的情况。

    • 通过遍历,找到位置在 m - 1 的节点 prev

  2. 反转从位置 mn 的子链表:

    • 初始化指针:

      • start:指向位置 m 的节点,反转后的尾部节点,即反转部分的第一个应该是反转后的最后一个

      • then:指向位置 m + 1 的节点,反转操作中的当前节点

    • 反转过程:

      • then 节点逐个移动,并调整指针连接。

  3. 重新连接反转后的子链表:

    • 反转完成后,prevnext 指针指向新的子链表头节点(即反转部分),startnext 指针指向反转部分之后的节点

5. 实现代码

5.1 关键函数实现

struct ListNode* reverseBetween(struct ListNode* head, int m, int n) {
    if (head == NULL) return NULL;

    struct ListNode dummy;
    dummy.next = head;
    struct ListNode* prev = &dummy;

    // 移动 prev 到位置 m-1
    for (int i = 1; i < m; i++) {
        prev = prev->next;
    }

    struct ListNode* start = prev->next;
    struct ListNode* then = start->next;

    // 反转位置 m 到 n 的节点
    for (int i = 0; i < n - m; i++) {
        start->next = then->next; //要反转的第一个数值start往后移
        then->next = prev->next; //当前数值的后面是m-1的后面那个数
        prev->next = then; //m-1数值的后面是当前数值
        then = start->next; //当前数值往后移
    }

    return dummy.next;
}

假如m=2,n=4:

第一次循环(i = 0):

  dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> NULL
                    ^    ^    ^
                  prev start then

第二次循环(i = 1):

 dummy -> 1 -> 4 -> 3 -> 2 -> 5 -> NULL
                   ^    ^    ^
                prev start then



5.2 完整的 C 语言代码

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点
struct ListNode {
    int val;
    struct ListNode *next;
};

// 反转指定区间的函数
struct ListNode* reverseBetween(struct ListNode* head, int m, int n) {
    if (head == NULL) return NULL;

    struct ListNode dummy;
    dummy.next = head;
    struct ListNode* prev = &dummy;

    // 移动 prev 到位置 m-1
    for (int i = 1; i < m; i++) {
        prev = prev->next;
    }

    struct ListNode* start = prev->next;
    struct ListNode* then = start->next;

    // 反转指定区间
    for (int i = 0; i < n - m; i++) {
        start->next = then->next;
        then->next = prev->next;
        prev->next = then;
        then = start->next;
    }

    return dummy.next;
}

// 创建新节点的辅助函数
struct ListNode* createNode(int value) {
    struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    if (!newNode) {
        perror("创建节点失败");
        exit(EXIT_FAILURE);
    }
    newNode->val = value;
    newNode->next = NULL;
    return newNode;
}

// 打印链表的辅助函数
void printList(struct ListNode* head) {
    struct ListNode* current = head;
    while (current != NULL) {
        printf("%d", current->val);
        if (current->next != NULL) {
            printf(" -> ");
        }
        current = current->next;
    }
    printf(" -> NULL\n");
}

// 释放链表内存
void freeList(struct ListNode* head) {
    struct ListNode* current = head;
    struct ListNode* temp;
    while (current != NULL) {
        temp = current->next;
        free(current);
        current = temp;
    }
}

// 测试代码
int main() {
    // 创建链表 {1 -> 2 -> 3 -> 4 -> 5}
    struct ListNode* head = createNode(1);
    head->next = createNode(2);
    head->next->next = createNode(3);
    head->next->next->next = createNode(4);
    head->next->next->next->next = createNode(5);

    printf("原始链表:\n");
    printList(head);

    int m = 2, n = 4;
    struct ListNode* modifiedHead = reverseBetween(head, m, n);

    printf("反转第 %d 到 %d 个节点后的链表:\n", m, n);
    printList(modifiedHead);

    // 释放内存
    freeList(modifiedHead);

    return 0;
}
 

5.3 代码说明

  • reverseBetween 函数:

    • dummy 节点:创建一个哑节点 dummy,指向链表头 head,用于简化边界条件的处理。

    • prev 指针:初始化为 dummy,用于定位到反转区间的前一个节点。

    • start 和 then 指针:start 指向反转区间的第一个节点,then 指向需要反转的下一个节点。

    • 反转过程:通过调整指针的指向,逐步将 then 节点插入到 prev 后面,实现局部反转。

  • 辅助函数:

    • createNode:创建新节点,初始化节点的值和指针。

    • printList:遍历并打印链表的所有节点值。

    • freeList:释放链表占用的内存,防止内存泄漏。

  • 主函数 main:

    • 创建示例链表并输出。

    • 调用 reverseBetween 函数反转指定区间的节点。

    • 输出反转后的链表结果。

    • 释放链表内存。

5.4 运行结果

        原始链表:
        1 -> 2 -> 3 -> 4 -> 5 -> NULL
        反转第 2 到 4 个节点后的链表:
        1 -> 4 -> 3 -> 2 -> 5 -> NULL

6. 时间和空间复杂度分析

  • 时间复杂度:O(n)

    • 需要遍历链表一次,找到位置 m,然后进行 n - m 次反转操作。
  • 空间复杂度:O(1)

    • 只使用了常量级的额外空间,主要是几个指针变量。

7. 总结

        通过上述方法,我们成功实现了对单链表指定区间的节点进行反转的操作。关键在于正确定位需要反转的区间,并在反转过程中维护好前后节点的连接关系。

        此方法不仅有效解决了本题,还可以应用于其他涉及链表部分反转或节点调整的题目。熟练掌握链表操作和指针的使用,对于处理类似问题至关重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

姚希瑶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值