【牛客网面试必刷TOP101】链表篇(一)

一、前言

链表是数据结构中重要的一个章节,他的重要性也不言而喻,在未来不管是笔试还是面试都会遇到这类的题目,所以接下来我就会把一些链表的常考的题目全部整理出来供大家学习指正。


二、学习刷题网站

点击下面链接即可进行刷题学习
开始刷题

1.推荐的原因

刷题网站何其多,但好的刷题网站却不多,以下几点就是我推荐的原因:
1️⃣全面

里面有很多资料,不管是刷题还是学习还是面经等等

2️⃣大众

首先用的人很多,可以看到很多的题解,其次如果有问题也会有很多人回答

3️⃣熟悉oj环境

我们以后找工作的时候很多公司都会用这个网站,我们可以提前熟悉环境


三、刷题

先说明一下一些题目取自牛客网面试必刷TOP101
里面的一些题目在我以前的文章详细写到过,如果没有用新的方法就不会再做讲解
链表题目(一)
链表题目(二)
环状链表

<1>反转链表

题目链接
描述:

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
数据范围:0 ≤ n ≤ 1000
要求:空间复杂度 O(1) ,时间复杂度 O(n) 。
如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:

在这里插入图片描述
示例1:

输入:{1,2,3}
返回值:{3,2,1}

示例2:

输入:{}
返回值:{}
说明:空链表则输出空

思路分析:

以前的文章里面讲过两个方法:
1.暴力改方向,三指针直接改
2.头插法
这里讲一下新方法:

递归法

递归主要是要把大事化小。用第一个元素来说,假设后面已经逆序,把后面的所有元素当成一个整体,把第一个元素移到最后就完成了逆序。然后继续第二个第三个元素递归下去,最终完成逆序。

而递归最重要的是结束条件,当递归到最后一个元素就已经全部完成了逆序。

struct ListNode* ReverseList(struct ListNode* pHead ) {
    // write code here
    if(pHead == NULL || pHead->next == NULL)
    {
        return pHead;
    }
    struct ListNode* tmp = ReverseList(pHead->next);
    pHead->next->next = pHead;
    pHead->next = NULL;
    return tmp;
}

这里注意pHead->next每次都是指向tmp链表的最后一个元素


<2>链表内指定区间反转

题目链接
描述:

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。
例如:
给出的链表为1→2→3→4→5→NULL, m=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}

思路分析:

①头插法

思路大体可以分为三个步骤
1️⃣ 先创建一个前序头指针,为了防止第一个元素也要逆序。
2️⃣用双指针prev和cur找到m的位置,prev就指向m的前一个位置,方便把cur的下一个元素移动到cur的前面
3️⃣对于从m到n这些个位置的节点,依次断掉指向后续的指针,反转指针方向。

这个方法的本意是通过cur的移动把所有后续元素移动到cur的前一个
如图所示:
在这里插入图片描述

struct ListNode* reverseBetween(struct ListNode* head, int m, int n ) {
    //表头
    struct ListNode* pNewHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    pNewHead->next = head;
    //前序节点
    struct ListNode* prev = pNewHead;
    //当前节点
    struct ListNode* cur = head;
    //找m
    for(int i = 1; i < m; i++)
    {
        prev = cur;
        cur = cur->next;
    }
    for(int i = m; i < n; i++)
    {
        struct ListNode* tmp = cur->next;
        cur->next = tmp->next;
        tmp->next = prev->next;
        prev->next = tmp;
    }
    head = pNewHead->next;
    free(pNewHead);
    pNewHead = NULL;
    return head;
}

②递归法

m == 1时,就是反转前n个元素,当m != 1时,我们把head的索引看作 1,那么如果把head->next节点的索引看作 1,那么相对于head->next就是从m - 1的位置开始反转。

再看当n == 1时,就是反转第一个元素,如果不是就往下递归,总能递归到n == 1
做法:
1️⃣先定义一个全局变量tmp,找到递归到第n个节点时,指向其后一个位置,然后把要反转的首节点(反转后的尾)连接到tmp
2️⃣递归找到首个反转的节点(m == 1)
3️⃣递归反转前n个节点,每个子问题的节点都连接tmp

还是用第一个例子来举例,当递归到最后一个子问题时:
在这里插入图片描述
然后前面的 2 同理也会链接到tmp。

static struct ListNode* tmp;

struct ListNode* reverse(struct ListNode* head, int n)
{
    //只颠倒第一个
    if(n == 1)
    {
        tmp = head->next;
        return head;
    }
    struct ListNode* node = reverse(head->next, n - 1);
    head->next->next = head;
    head->next = tmp;
    return node;
}

struct ListNode* reverseBetween(struct ListNode* head, int m, int n ) {
    if(m == 1)
    {
        return reverse(head, n);
    }
    struct ListNode* node = reverseBetween(head->next, m - 1, n - 1);
    head->next = node;
    return head;
}

<3>链表中的节点每k个一组翻转

题目链接
描述:

将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。

数据范围:0 ≤ n ≤ 2000,1 ≤ k ≤ 2000,链表中每个元素都满足 0 ≤ val ≤ 1000,要求空间复杂度 O(1),时间复杂度 O(n)。
例如:给定的链表是 1→2→3→4→5
对于 k = 2 , 你应该返回 2→1→4→3→5
对于 k = 3 , 你应该返回 3→2→1→4→5

示例1:

输入:{1,2,3,4,5},2
返回值:{2,1,4,3,5}

示例2

输入:{},1
返回值:{}

思路分析:

①头插法

跟上面一个题类似,头插的方法也相同,不做过多赘述,要注意的是判断end是否超过结尾。

struct ListNode* reverseKGroup(struct ListNode* head, int k ) {
    struct ListNode* newHead = (struct ListNode*)malloc(sizeof(struct ListNode));
    newHead->next = head;
    struct ListNode* end = head, *prev = newHead;
    while(end)
    {
        int i = 1;
        for(i = 1; i < k && end != NULL; i++)
        {
            end = end->next;
        }
        if(end == NULL)
            break;
        int j = 1;
        for(j = 1; j < k; j++)
        {
            struct ListNode* tmp = head->next;
            head->next = tmp->next;
            tmp->next = prev->next;
            prev->next = tmp;
        }
        prev = head;
        head = head->next;
        end = head;
    }
    head = newHead->next;
    free(newHead);
    newHead = NULL;
    return head;
}

②递归法

把每一段分开逆序,每一段的逆序前面已经做过,那么接下来就是把每段逆序后的结果链接到一起。
要注意的是:
例如第一段逆序完后head为尾部,那么链接下一段就是head->next = 子问题
结束的条件是当end走到NULL。

struct ListNode* _reverseKGroup(struct ListNode* head, struct ListNode* end)
{
    if(head == end || head->next == end)
    {
        return head;
    }
    struct ListNode* tmp = _reverseKGroup(head->next, end);
    head->next->next = head;
    head->next = end;
    return tmp;
}

struct ListNode* reverseKGroup(struct ListNode* head, int k ) {
    struct ListNode* end = head;
    for(int i = 1; i < k && end; i++)
    {
        end = end->next;
    }
    if(end == NULL)
        return head;
    //用ret接收头部
    struct ListNode* ret = _reverseKGroup(head, end->next);
    //head为尾,链接下一段
    head->next = reverseKGroup(head->next, k);
    return ret;
}

四、小结

链表的题目一定要画图,如果递归不知道怎么写就分析最后一个子问题情况,问题就可以迎刃而解。递归的经典例题在之前的文章也有讲解:
递归经典题目详解



  • 76
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 115
    评论
以下是用C语言实现奇偶链表的代码和注释: ```c #include <stdio.h> #include <stdlib.h> // 定义链表节点结构体 typedef struct ListNode { int val; struct ListNode *next; } ListNode; // 创建链表函数,返回链表头指针 ListNode* createList() { ListNode *head = (ListNode*)malloc(sizeof(ListNode)); // 创建头节点 head->next = NULL; // 头节点的next指针指向NULL ListNode *tail = head; // 定义尾指针,初始指向头节点 int data; scanf("%d", &data); while (data != -1) { // 输入-1表示链表输入结束 ListNode *cur = (ListNode*)malloc(sizeof(ListNode)); // 创建新节点 cur->val = data; // 新节点的值为输入的值 cur->next = NULL; // 新节点的next指针指向NULL tail->next = cur; // 尾指针的next指针指向新节点 tail = cur; // 尾指针指向新节点 scanf("%d", &data); } return head; // 返回头指针 } // 分离奇偶链表函数,返回新链表头指针 ListNode* oddEvenList(ListNode* head) { if (head == NULL || head->next == NULL) { // 如果链表为空或只有一个节点,直接返回原链表头指针 return head; } ListNode *odd = head; // 定义奇数节点指针,初始指向头节点 ListNode *even = head->next; // 定义偶数节点指针,初始指向第二个节点 ListNode *evenHead = even; // 定义偶数链表头指针,初始指向第二个节点 while (even != NULL && even->next != NULL) { // 遍历链表,将奇数节点和偶数节点分别连接起来 odd->next = even->next; // 奇数节点的next指针指向下一个奇数节点 odd = odd->next; // 奇数节点指针指向下一个奇数节点 even->next = odd->next; // 偶数节点的next指针指向下一个偶数节点 even = even->next; // 偶数节点指针指向下一个偶数节点 } odd->next = evenHead; // 将奇数链表和偶数链表连接起来 return head; // 返回原链表头指针 } int main() { ListNode *head = createList(); // 创建链表 head = oddEvenList(head); // 分离奇偶链表 ListNode *cur = head->next; // 定义指针,指向第一个节点 while (cur != NULL) { // 遍历链表,输出每个节点的值 printf("%d ", cur->val); cur = cur->next; } return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

命由己造~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值