链表内指定区间反转链表

1、描述

这道题是在牛客上看到的,原题如下

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL, m=2,n=4m=2,n=4,
返回 1→4→3→2→5→NULL.

数据范围: 链表长度 0 < size ≤1000, 0<m≤n≤size,链表中每个节点的值满足 val≤1000
要求:时间复杂度 O(n) ,空间复杂度 O(n)
进阶:时间复杂度 O(n),空间复杂度 O(1)
示例1
输入:
{1,2,3,4,5},2,4
复制
返回值:
{1,4,3,2,5}
示例2
输入:
{5},1,1
复制
返回值:
{5}

2、思路

在原题编译器中给出的信息为:
/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 * };
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 *
 * @param head ListNode类
 * @param m int整型
 * @param n int整型
 * @return ListNode类
 */
struct ListNode* reverseBetween(struct ListNode* head, int m, int n ) 
{
    // write code here
}
反转链表,方法有很多,我这里直接使用了头插法,即插入每个元素都插在第一个后面,这样自然就形成了反转效果。

那么区域性的反转链表,可以记录该区域前一个位置和后一个位置,接着将需要操作的区域进行反转,然后再与记录好的两个节点链接,实现反转效果。

但是这种写法非常麻烦,于是我整理思路以后决定记录下反转的前一个位置(这里用before表示),以及需要翻转的长度(这里用x表示)。操作时只需首先找到要进行操作的前一个位置,后面的每个节点都利用头插法,插在befor的下一个位置。这个操作执行x后停止,刚好就完成了区域反转的操作。这样做的好处是简单易懂,复杂度也低。

需要注意的是,这次处理的链表头节点也放了数据进行使用,在这种情况下。因为我们需要找到前一个位置,而头节点没有上一个节点,所以为了方便处理,可以自己创建一个临时头节点,而原先的头节点作为临时头节点的下一个节点。这样就能用一种方法统一处理任意位置。

另外,当 m = n 时,原链表并没有动,我们可以单独判断这种情况,然后直接返回头节点。

3、接下来就是愉快的代码环节

这里方便展示效果,我选择在本地环境编写,只是简略补齐了必要代码(除了答题的reverseBetween函数,其他函数没有很严谨处理可能出错的情况):

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

//声明结构体
struct ListNode
{
    int val;
    struct ListNode *next;
};

//创建链表
struct ListNode *create(int data)
{
    struct ListNode *head = (struct ListNode *)malloc(sizeof(struct ListNode));
    head->next = NULL;
    head->val = data;
    return head;
}

//翻转链表(核心)
struct ListNode *reverseBetween(struct ListNode *head, int m, int n)
{
    // write code here
    
    //处理一些错误情况
    if (head == NULL || m > n)
    {
        return NULL;
    }
    //m = n 时反转结果并没有与原链表产生差异,直接返回head
    if (m == n)
    {
        return head;
    }

    struct ListNode *temp_head; //申请一片空间作为整个链表表头,解决第一个位置的翻转问题
    struct ListNode *before;    //记录翻转节点的前一个节点

    struct ListNode *change_head; //记录翻转节点的节点头(未翻转前,反转后他就是反转区域的最后一个节点了),目的是反转完成后拼接后续不翻转的链表

    struct ListNode *p; //反转操作时使用,记录的是 进行反转 这个 过程中 的 第一个反转节点
    struct ListNode *q; //反转操作时使用,记录的是 进行反转 这个 过程中 的 第二个反转节点

    int x = n - m + 1; //翻转进行次数(即需要翻转多少个节点)

    //初始化
    temp_head = (struct ListNode *)malloc(sizeof(struct ListNode)); //申请临时头节点
    temp_head->val = 10086; //随便填了一个,这个值没有用
    temp_head->next = head;//头节点接在临时头节点后面

    //从第一个节点开始寻找要翻转节点的前一个节点
    change_head = temp_head;
    while (m-- > 1) // 第m个是反转节点位置,那么m-1就是反转节点的上一个节点
    {
        change_head = change_head->next;
    }
    //记录下翻转节点前一个节点
    before = change_head;

    p = change_head->next; //记录下要翻转节点

    change_head = change_head->next; //保存翻转区域的第一个节点,反转操作结束以后他是最后一个节点,需要拼接后续没有操做的链表

    //开始翻转,头插
    while (x--)
    {
        q = p->next;
        p->next = before->next;
        before->next = p;
        p = q;
    }
    change_head->next = q; //拼接后续没有反转的链表
    head = temp_head->next; //头节点回到临时头节点的下一个位置
    free(temp_head);//释放掉临时头节点
    return head; //返回头节点,操作结束
}

//插入,这里为了方便直接尾插
int insert(struct ListNode *head, int data)
{
    if (head == NULL)
    {
        return -1;
    }
    struct ListNode *new = (struct ListNode *)malloc(sizeof(struct ListNode));
    new->next = NULL;
    new->val = data;

    struct ListNode *p = head;
    while (p->next != NULL)
        p = p->next;
    p->next = new;
    return 0;
}

//打印链表内容
void show(struct ListNode *head)
{
    struct ListNode *p = head;
    while (p != NULL)
    {
        printf("%d ", p->val);
        p = p->next;
    }
    printf("\n");
}

int main()
{
    struct ListNode *head = create(1);
    for (int i = 2; i < 10; i++)
    {
        insert(head, i);
    }
    printf("原链表        :");
    show(head);
    
    //测试 2,8 位置
    head = reverseBetween(head, 2, 8);
    printf("2,8位置翻转后:");
    show(head);
    head = reverseBetween(head, 2, 8); //再次翻转是为了回到原来的样子,方便后续观看
    
    //测试 1,1 位置
    head = reverseBetween(head, 1, 1);
    printf("1,1位置翻转后:");
    show(head);
    head = reverseBetween(head, 1, 1);
    
    //测试 1,9 位置
    head = reverseBetween(head, 1, 9);
    printf("1,9位置翻转后:");
    show(head);
    head = reverseBetween(head, 1, 9);
    
}

4、结语

最后测试用的例子比较少且简单,因为在牛客上编译通过,就没有做太多的例子。实际测试,可以利用空链表,只有一两个节点的链表等多种情况的链表测试几次。因为我编程时也遇到前几个测试通过,后续一些链表没法通过的情况。
最后,我用的只是我能想到的简单方式完成了区域反转操作,大家感兴趣的话可以区牛客网实际体会一下,题目叫做 链表内指定区间反转。后续的每K个为一组进行反转也很有意思。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值