【数据结构】链表OJ第一篇 —— 移除链表元素 && 反转链表 && 合并两个有序链表

简介: 【数据结构】链表OJ第一篇 —— 移除链表元素 && 反转链表 && 合并两个有序链表

1. 移除链表元素


链接203. 移除链表元素


示例1

8f07f4090fe0a821e19e4bac9a4c101b.jpeg


   输入:head = [1,2,6,3,4,5,6], val = 6

   输出:[1,2,3,4,5]


示例2:

   输入:head = [], val = 1

   输出:[]


示例3:

   输入:head = [7,7,7,7], val = 7

   输出:[]


提示:

   列表中的节点数目在范围 [0, 104] 内

   1 <= Node.val <= 50

   0 <= val <= 50


思路1(精讲):


考察的为单链表元素的删除。


遍历链表,用变量 prev 记录遍历链表时每个节点的上一个节点。变量 cur 为本次节点。


如果第一个节点的值域和 val 相等,为 头删 情况,此时记录本节点的下一个节点,将头变为本节点的下一个节点,释放本节点,迭代 cur 。


如果节点的值和 val 相等且 不为头删 情况,那么将 prev 的 next 和 cur 的 next 链接(prev->next = cur->next),然后释放 cur 节点,将cur变为 prev 的next,也就是之前当前节点的下一个节点。


如果这两个条件不满足,则将 prev 和 cur 迭代向后走。


最后返回 head 头。


d98f8a537172506fdfd5b7b4ce1cc5f4.pngf1b107bfe80c3d3e8657cd8acd5b5b86.png

26cff32ce7b691a84f348740f1fca273.png


思路2(精讲):


尾插法。


总体思路是这样的,遍历链表,将节点的值不为 val 的节点尾插到新链表中,如果等于 val 就迭代往后走,直到链表为空。


但是尾插需要特殊考虑空链表尾插的情况,实际上写起来还是比较烦的,所以这时我们就可以用到上篇博客中提到的哨兵位(哑节点)。


先给定一个 cur 作为遍历原链表的指针。


我们先动态开辟一个哨兵位 dummy 。每次尾插时,都需要找到尾部,那么再给定一个 tail 让其等于 dummy 。这样改变 tail 的链接的时候,也就相当于改变了新链表。


接下来就是常规操作,使用 cur 遍历链表,如果 cur 对应节点的值不等于 val ,那么就对新链表进行尾插,这时由于链表一定不为空,于是直接将 tail->next 链接为 cur 即可,再使 tail 、cur 向后迭代。

如果 cur 对应节点的值等于 val ,那么就说明这为需要删除的节点,那么先拷贝 cur 的下一个节点,再释放该节点,再cur 迭代往后走。


注意:如果 tail 最后链接的节点不是原链表的最后一个节点,那么 tail->next 还链接着原链表,而一旦原链表的节点已经被释放,这就导致了野指针。所以需要断开链接,链到空(tail->next = NULL)。


b267eeb881558e9e582fb9fb6d26a6d5.png

1ea45961ed333fd1e4b3375583648ff9.png


2. 反转链表


链接206. 反转链表


描述


给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。


示例1

4e55e3e798dd631b91d2210635f4e33b.jpeg



输入:head = [1,2,3,4,5]

输出:[5,4,3,2,1]

示例2



b971a2e36b3f0f72af5238f8dfd6a271.jpeg


   输入:head = [1,2]

   输出:[2,1]


示例3:

   输入:head = []

   输出:[]


思路1(精讲):


迭代法。


我们想象一下,原先链表的第一个节点链接着第二个节点,那我们是否可以将第二个节点链接到第一个节点?再将其他元素逐次链接,第一个节点链接到空指针。就像这样:NULL ← ① ← ②

这当然可以,因为这就是当前思路的主要方法。


我们需要三个变量,n1 记录上一个节点, n2 记录当前节点,n3记录下一个节点。

然后通过 n2 遍历链表,将链表节点逐个反转,然后利用 n2 使 n1 迭代到当前节点的位置,利用 n3 使 n2 迭代到下一个节点,这两个步骤的次序一定不能乱。上面两个步骤完成后,如果 n3 不为 NULL ,那么让 n3 也迭代到它的下一个节点处。


最后返回 n1 就是反转后的链表。


注意点:空链表需要特判。


c0dde9c9f25177d73c79b90c471a7474.png


90e248f88aad582c3204ba396b2acb85.png


思路2(精讲):


头插法。


给定 cur 用来遍历链表,newhead为链表的新头。


遍历链表,记录 cur 的下一个节点(cur->next),然后将这个节点头插到 newhead前,将 newhead 赋值为 cur,到最后newhead 就是之前链表的最后一个节点,返回即可。


a07e287206858b60a8fd5487a1e3e29f.png

12c9639728546cec6a582db2821dd755.png




3. 合并两个有序链表


链接21. 合并两个有序链表


描述


将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。


示例1


e5c00c891bece2268b9ad8875dfa40ba.png



   输入:l1 = [1,2,4], l2 = [1,3,4]

   输出:[1,1,2,3,4,4]


示例2:

   输入:l1 = [], l2 = []

   输出:[]


示例3:

   输入:l1 = [], l2 = [0]

   输出:[0]


提示:

   两个链表的节点数目范围是 [0, 50]

   -100 <= Node.val <= 100

   l1 和 l2 均按 非递减顺序 排列


思路(精讲):


我们之前做过一道题目,叫做合并两个有序数组。其中有一种方法是创建一个新数组,然后遍历两个数组,将两个数组的较小的元素放置到新数组中。一个数组遍历完,将没有放置的元素放置到新数组中,然后拷贝回原数组。


那么我们这道题能否借鉴它的思路?


我们这道题为合并两个有序链表,链表和数组不同,数组形式的一种方法需要我们创建一个新数组。但是对于链表而言我们可以通过指针改变链接关系,所以我们不需要创建新链表,只需要修改即可。


有序数组的做法是将较小元素逐个尾插到新数组中,那么我们也可以将较小元素尾插到链表中呀。


但是尾插就有两个问题,当尾插的时候,我们需要找链表的尾,而且当链表为空时,需要特殊处理。


为了避免每次找链表的尾,那么我们就给定一个 tail,这样只要将 tail 迭代就可以。


那么链表为空如何处理?难道在循环中特判,然后每次迭代的时候都判断一次,那也太麻烦了,而且也会代码冗余。


那么这时就又要用到哨兵位(头结点)了,我们给一个哨兵位 head,它也不存储数据,那么不就可以了?但是注意有效数据从 head->next 开始。


注意:哨兵位需要释放,否则会造成内存泄漏。


12725c342a12bdaf7cef059dd5977cc4.png

1b8578cf416438b26e1639d2a596ad24.png


代码

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* head = NULL, *tail = NULL;
    if (list1 == NULL)
    {
        return list2;
    } 
    if (list2 == NULL)
    {
        return list1;
    }
    // 哨兵位
    // 这里 tail 也需要动态开辟一下
    // 因为不在迭代时进行第一次插入的处理
    // tail 一开始为空指针,会报错
    head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));   
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            tail->next = list1; // 当前结点链表的尾链接到 list1
            tail = list1; // 链表的尾变成 list1
            list1 = list1->next; // list1 并没有改变,list1 迭代到list1的下一个节点
        }
        else
        {
            tail->next = list2;
            tail = list2;
            list2 = list2->next;
        }
    }
    // 未放置完的元素
    // 这里和数组的完全不一样
    // 链表是串联的,所以只需要把当前节点给到tail->next
    // 就可以全部串联
    if (list1)
    {
        tail->next = list1;
    }
    if (list2)
    {
        tail->next = list2;
    }
    // 释放哨兵位
    struct ListNode* ans = head->next;
    free(head); 
    return ans;
}


这道题目不使用哨兵位也能写,但是使用这种方法时,需要处理一下链表第一次合并时尾插的情况,大体思路和带哨兵位差不多,只是需要注意一下细节,这里我就不多赘述了,大家可以自己试试,下面贴下截图和代码:


3bf0e6b72dfd9ccfc17967dcc41b202a.png

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    if (list1 == NULL)
        return list2;
    if (list2 == NULL)
        return list1;
    struct ListNode *head, *tail;
    head = tail = NULL;
    while (list1 && list2)
    {
        if (list1->val < list2->val)
        {
            // 第一次合并
            if (tail == NULL)
            {
                head = tail = list1;
            }
            else
            {
                tail->next = list1;
                tail = tail->next;
            }
            list1 = list1->next;
        }
        else
        {
            // 第一次合并
            if (tail == NULL)
            {
                head = tail = list2;
            }
            else
            {
                tail->next = list2;
                tail = tail->next;
            }
            list2 = list2->next;
        }
    }
    if (list1)
        tail->next = list1;
    if (list2)
        tail->next = list2;
    return head;
}



到这里,本篇博客就到此结束了,建议大家在看完博客后,再下去写一写。从 理解 → 独立想出思路 → 画图 → 完整实现代码。会写一道题目不是在看完题解后,因为记住了代码,默写下来;而是能自己想出思路,能画出图,并自我实现,这样才算掌握。多写多练多画图,才能学好数据结构。

下篇博客我会继续更新链表的OJ题,我们敬请期待~

如果觉得anduin写的还不错的话,还请一键三连!如有错误,还请指正!

我是anduin,一名C语言初学者,我们下期见!



相关文章
|
4天前
|
机器学习/深度学习 算法 测试技术
【单调栈】3113. 边界元素是最大值的子数组数目
【单调栈】3113. 边界元素是最大值的子数组数目
|
11天前
|
存储
数据结构第二课 -----线性表之单向链表
数据结构第二课 -----线性表之单向链表
|
1天前
|
Java C语言
剑指offer(牛客)——合并两个排序的链表
剑指offer(牛客)——合并两个排序的链表
6 1
|
1天前
|
存储 算法 Java
数据结构与算法 数组和链表
数据结构与算法 数组和链表
7 0
|
2天前
|
存储 Java
深入浅出数据结构之链表
深入浅出数据结构之链表
|
2天前
|
C++
数据结构(双链表
数据结构(双链表
6 1
|
2天前
leetcode代码记录(移除链表元素
leetcode代码记录(移除链表元素
8 0
|
5天前
|
存储 缓存
[数据结构]~双向+循环链表从(0~1)
[数据结构]~双向+循环链表从(0~1)
|
11天前
|
存储
数据结构第三课 -----线性表之双向链表
数据结构第三课 -----线性表之双向链表
|
12天前
|
存储 Java
数据结构奇妙旅程之顺序表和链表
数据结构奇妙旅程之顺序表和链表