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


    一、前言

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


    二、学习刷题网站

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

    1.推荐的原因

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

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

    2️⃣大众

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

    3️⃣熟悉oj环境

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


    三、刷题

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

    <1>合并两个排序的链表

    题目链接
    描述:

    输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
    数据范围:0 ≤ n ≤ 1000,−1000 ≤ 节点值 ≤ 1000
    要求:空间复杂度 O(1),时间复杂度 O(n)
    如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6},转换过程如下图所示:

    在这里插入图片描述

    或输入{-1,2,4},{1,3,4}时,合并后的链表为{-1,1,2,3,4,4},所以对应的输出为{-1,1,2,3,4,4},转换过程如下图所示:

    在这里插入图片描述

    示例1:

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

    示例2:

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

    示例3:

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

    思路分析:
    前面的文章讲解过这道题,用的是迭代法,用两个指针,找到小就往后移动,直到一个到空指针,再链接到另一个非空指针节点。
    现在我们要用另一种方法:

    递归法

    首先想明白两个问题:

    1️⃣递归函数结束的条件是什么?
    2️⃣递归函数一定是缩小递归区间的,那么下一步的递归区间是什么?

    结束的条件当然是为空,对于第二个问题:跟迭代方法中的一样,如果PHead1的所指节点值小于等于pHead2所指的结点值,那么phead1后续节点和pHead节点继续递归

    struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2 ) {
        if(pHead1 == NULL)
        {
            return pHead2;
        }
        if(pHead2 == NULL)
        {
            return pHead1;
        }
        struct ListNode* head;
        if(pHead1->val < pHead2->val)
        {
            head = pHead1;
            head->next = Merge(pHead1->next, pHead2);
        }
        else
        {
            head = pHead2;
            head->next = Merge(pHead1, pHead2->next);
        }
        return head;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    <2> 合并k个已排序的链表

    题目链接
    描述:

    合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。
    数据范围:节点总数满足 数据范围:节点总数满足 0 ≤ n ≤ 10^5,链表个数满足 1 ≤ k ≤ 10^5,每个链表的长度满足1 ≤ len ≤ 200 ,每个节点的值满足 |val| <= 1000
    要求:时间复杂度 O(nlogk)

    示例1

    输入:[{1,2,3},{4,5,6,7}]
    返回值:{1,2,3,4,5,6,7}

    示例2

    输入:[{1,2},{1,4,5},{6}]
    返回值:{1,1,2,4,5,6}

    思路分析:

    归并排序思想

    上一道题是两个链表合并,我们可以用归并思想,用俩指针,小的元素合并到大链表。
    这道题是k个链表,我们也可以按照上述思路合并,然后新链表再与后一个继续合并,如此循环,但是这样效率太低了。

    归并排序是什么?简单来说就是将一个数组每次划分成等长的两部分,对两部分进行排序即是子问题。对子问题继续划分,直到子问题只有1个元素。还原的时候呢,将每个子问题和它相邻的另一个子问题利用上述双指针的方式,1个与1个合并成2个,2个与2个合并成4个,因为这每个单独的子问题合并好的都是有序的,直到合并成原本长度的数组。

    在我以前的文章也有详细讲解:
    八大排序,你都掌握了吗?

    struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2 ) {
        if(pHead1 == NULL)
        {
            return pHead2;
        }
        if(pHead2 == NULL)
        {
            return pHead1;
        }
        struct ListNode* head;
        if(pHead1->val < pHead2->val)
        {
            head = pHead1;
            head->next = Merge(pHead1->next, pHead2);
        }
        else
        {
            head = pHead2;
            head->next = Merge(pHead1, pHead2->next);
        }
        return head;
    }
    
    struct ListNode* DivMerge(struct ListNode** list, int left, int right)
    {
        if(left > right)
        {
            return NULL;
        }
        //中间一个的情况
        if(left == right)
        {
            return list[left];
        }
        int mid = (left + right) >> 1;
        struct ListNode* head1 = DivMerge(list, left, mid);
        struct ListNode* head2 = DivMerge(list, mid + 1, right);
        return Merge(head1, head2);
    }
    
    struct ListNode* mergeKLists(struct ListNode** lists, int listsLen ) {
        return DivMerge(lists, 0, listsLen - 1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    <3>删除链表的倒数第n个节点

    描述

    给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针
    例如,
    给出的链表为: 1→2→3→4→5, n= 2.
    删除了链表的倒数第 n 个节点之后,链表变为1→2→3→5.

    数据范围: 链表长度 10000 ≤ n≤ 1000,链表中任意节点的值满足0 ≤ val ≤ 100
    要求:空间复杂度 O(1),时间复杂度 O(n)
    备注:
    题目保证 n 一定是有效的

    示例1

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

    思路分析:

    快慢指针法

    前面的文章写过类似的,只不过这个是要删除,这个就要考虑头指针位置的删除,直接加一个前序节点就行。
    还有慢指针得找到他的前驱和后继节点。

    struct ListNode* removeNthFromEnd(struct ListNode* head, int n ) {
        struct ListNode* slow = head, *fast = head;
        struct ListNode* NewHead = (struct ListNode*)malloc(sizeof(struct ListNode));
        NewHead->next = head;
        struct ListNode* prev = NewHead, *next = slow->next;
        //fast先走n步
        while(n--)
        {
            fast = fast->next;
        }
        while(fast)
        {
            prev = slow;
            slow = slow->next;
            next = slow->next;
            fast = fast->next;
        }
        prev->next = next;
        free(slow);
        head = NewHead->next;
        free(NewHead);
        return head;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    <4>链表相加(二)

    题目链接
    描述

    假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
    给定两个这种链表,请生成代表两个整数相加值的结果链表。
    数据范围:0 ≤ n,m ≤ 1000000,链表任意值 90 ≤ val ≤ 9
    要求:空间复杂度 O(n),时间复杂度 O(n)
    例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。

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

    输入:[9,3,7],[6,3]
    返回值:{1,0,0,0}
    说明:
    如题面解释

    示例2

    输入:[0],[6,3]
    返回值:{6,3}

    备注:

    1 ≤ n,m ≤ 10^6

    思路分析:
    我们可以结合之前做过的题目,把两个链表反转,然后依次相加,相加的结果头插到新的链表中,即可得出答案。
    在这里插入图片描述

    struct ListNode* reverse(struct ListNode* head)
    {
        if(head == NULL || head->next == NULL)
        {
            return head;
        }
        struct ListNode* tmp = reverse(head->next);
        head->next->next = head;
        head->next = NULL;
        return tmp;
    }
    
    struct ListNode* addInList(struct ListNode* head1, struct ListNode* head2 ) {
        if(head1 == NULL)
            return head2;
        if(head2 == NULL)
            return head1;
        head1 = reverse(head1);
        head2 = reverse(head2);
        struct ListNode* tail = NULL;
        int add = 0;
        while(head1 && head2)
        {
            int ret = head1->val + head2->val + add;
            add = 0;
            if(ret >= 10)
            {
                ret %= 10;
                add = 1;
            }
            struct ListNode* node = (struct ListNdoe*)malloc(sizeof(struct ListNode));
            node->val = ret;
            node->next = tail;
            tail = node;
            head1 = head1->next;
            head2 = head2->next;
        }
        if(head1 == NULL && head2 == NULL)
        {
            return tail;
        }
        struct ListNode* Nhead = head1 == NULL ? head2 : head1;
        while(Nhead)
        {
            int ret = Nhead->val + add;
            add = 0;
            if(ret >= 10)
            {
                ret %= 10;
                add = 1;
            }
            struct ListNode* node = (struct ListNdoe*)malloc(sizeof(struct ListNode));
            node->val = ret;
            node->next = tail;
            tail = node;
            Nhead = Nhead->next;
        }
        if(add == 1)
        {
            struct ListNode* node = (struct ListNdoe*)malloc(sizeof(struct ListNode));
            node->val = 1;
            node->next = tail;
            tail = node;
        }
        return tail;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    四、小结

    这些题目都是相互关联,要达到做一个会多个的效果。



  • 相关阅读:
    Python的编辑器VScode中文设置和Hello World
    网易游戏学院系列——书籍《游戏设计》【笔记】
    Grafana设置默认主页
    C++:模板进阶
    排队算法的matlab仿真,带GUI界面
    非零基础自学Java (老师:韩顺平) 第4章 运算符 4.12 进制 && 其他进制转十进制 && 十进制转其他进制
    水管安装过滤器笔记
    有一个整数单链表L,设计一个算法逆置L中所有结点,Java
    微服务与中间件系列——容器技术Docker
    【STL】list常见用法及模拟实现(附完整源码)
  • 原文地址:https://blog.csdn.net/qq_66314292/article/details/126275720