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

牛客网题目

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

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

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

相关推荐
从以前37 分钟前
【算法题解】Bindian 山丘信号问题(E. Bindian Signaling)
开发语言·python·算法
不白兰41 分钟前
[代码随想录23回溯]回溯的组合问题+分割子串
算法
御风@户外1 小时前
质数生成函数、质数判断备份
算法·acm
闻缺陷则喜何志丹2 小时前
【C++动态规划】1105. 填充书架|2104
c++·算法·动态规划·力扣·高度·最小·书架
Dong雨2 小时前
六大排序算法:插入排序、希尔排序、选择排序、冒泡排序、堆排序、快速排序
数据结构·算法·排序算法
达帮主2 小时前
7.C语言 宏(Macro) 宏定义,宏函数
linux·c语言·算法
是十一月末2 小时前
机器学习之KNN算法预测数据和数据可视化
人工智能·python·算法·机器学习·信息可视化
chenziang12 小时前
leetcode hot100 路径总和
算法
lyx1426062 小时前
leetcode 3083. 字符串及其反转中是否存在同一子字符串
算法·leetcode·职场和发展
茶猫_2 小时前
力扣面试题 39 - 三步问题 C语言解法
c语言·数据结构·算法·leetcode·职场和发展