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

牛客网题目

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. 总结

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

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

相关推荐
shymoy18 分钟前
Radix Sorts
数据结构·算法·排序算法
风影小子27 分钟前
注册登录学生管理系统小项目
算法
黑龙江亿林等保29 分钟前
深入探索哈尔滨二级等保下的负载均衡SLB及其核心算法
运维·算法·负载均衡
lucy1530275107932 分钟前
【青牛科技】GC5931:工业风扇驱动芯片的卓越替代者
人工智能·科技·单片机·嵌入式硬件·算法·机器学习
杜杜的man1 小时前
【go从零单排】迭代器(Iterators)
开发语言·算法·golang
小沈熬夜秃头中୧⍤⃝1 小时前
【贪心算法】No.1---贪心算法(1)
算法·贪心算法
木向2 小时前
leetcode92:反转链表||
数据结构·c++·算法·leetcode·链表
阿阿越2 小时前
算法每日练 -- 双指针篇(持续更新中)
数据结构·c++·算法
skaiuijing2 小时前
Sparrow系列拓展篇:对调度层进行抽象并引入IPC机制信号量
c语言·算法·操作系统·调度算法·操作系统内核
Star Patrick2 小时前
算法训练(leetcode)二刷第十九天 | *39. 组合总和、*40. 组合总和 II、*131. 分割回文串
python·算法·leetcode