【C语言数据结构】单链表经典OJ题(题源:LeetCode206. 反转链表,21. 合并两个有序链表)

作者:热爱编程的小y
专栏:C语言数据结构
座右铭:能击败你的只能是明天的你

OJ.1

题目

206. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2] 输出:[2,1]
示例 3:
输入:head = [] 输出:[]

解析

先画图分析一波

【C语言数据结构】单链表经典OJ题(题源:LeetCode206. 反转链表,21. 合并两个有序链表)_第1张图片

如图所示,要想实现链表的反转,我们需要让第一个节点与NULL链接,第二个节点与第一个节点链接,第三个节点与第二个节点链接……以此类推。根据以上步骤,我们需要一个参数来表示当前节点,还需要一个参数标记前一个节点的位置用于链接,链接完成之后还需要将节点后移,因此还需要一个参数标记后一个节点的位置。由此不难设出三个指针变量n1,n2,n3。n2指向三者的中间节点,它负责重新链接,因此n2的初始位置应该在第一个节点处,那么n3初始位置就在第二个节点处,n1的位置没有节点,因此它的初始值为NULL。

【C语言数据结构】单链表经典OJ题(题源:LeetCode206. 反转链表,21. 合并两个有序链表)_第2张图片

当n2链接完成之后,也就是 n2->next = n1 之后,n1来到n2的位置;也就是 n1 = n2 ,n2来到n3的位置,也就是 n2 = n3;最后n3来到下一个节点处,n3 = n3->next。

【C语言数据结构】单链表经典OJ题(题源:LeetCode206. 反转链表,21. 合并两个有序链表)_第3张图片

如此往复几次之后n3肯定会来到一个NULL的位置,此时循环并没有停止,因为n2刚来到最后一个节点处,最后一个节点还未完成重新链接,因此只有当n2来到NULL的位置时才停止循环。

但这时候就有个问题,n3 = NULL 时没有停止的话,n3->next 还需要继续赋值给n3,但此时n3->next 已经不存在了,程序就会出现问题,因此需要加上一个判断条件,当n3为NULL时,直接返回值。

最后由于我们设了三个指针变量,因此链表至少含有两个节点,那么就需要考虑0节点和1节点的情况

由此可得最终代码如下:

struct ListNode* reverseList(struct ListNode* head)
{
    if(head == NULL)
        return NULL;
    if(head->next == NULL)
        return head;
    struct ListNode* n1 = NULL;
    struct ListNode* n2 = head;
    struct ListNode* n3 = head->next;   
    while(n2 != NULL)
    {
        n2->next = n1;
        n1 = n2;
        if(n3 == NULL)
            return n2;//n3为空直接返回值
        n2 = n3;
        n3 = n3->next;
    }
    return n2;
}

OJ.2

题目

21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4] 示例 2:
输入:l1 = [], l2 = [] 输出:[] 示例 3:
输入:l1 = [], l2 = [0] 输出:[0]
【C语言数据结构】单链表经典OJ题(题源:LeetCode206. 反转链表,21. 合并两个有序链表)_第4张图片

如图所示,合并两个链表。有两种思路,一种是在链表1或者链表2内部插入另外一个链表,但这就涉及了在链表中间插入,要找到前一节点以及后一节点,还要考虑头节点和尾节点的情况,因此代码实现起来十分复杂,动辄八九十甚至上百行代码。既然如此,那我们何不另寻一种更简单高效的方法呢。

第二种思路就是额外创立一个新链表,因为链表和数组不同,链表的额外开辟成本很低,不需要考虑空间、长度,只需要一个新的节点即可。

方法1

我们定义指针d1,d2分别用于标记链表list1,list2的节点的位置。之后定义一个空指针head表示新链表的头节点位置,存放链表list1,list2的首节点的较小值。定义一个空指针tail表示新链表尾节点的位置,初始值与head相同。

若d1较小,则d1指向下一个节点,若d2较小,则d2指向下一个节点,之后tail指向的节点链接d1、d2的较小值,tail再指向下一个节点。如此往复直到d1,d2其一出现NULL值停止,再把没有遍历完的链表接到新链表的后面,即可完成合并。

由此可得代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    if(list1 == NULL)
        return list2;
    if(list2 == NULL)
        return list1;
    struct ListNode* d1 = list1;
    struct ListNode* d2 = list2;
    struct ListNode* head = NULL;
    struct ListNode* tail = NULL;
    while(d1 && d2)
    {
        if(d1->val >= d2->val)
        {
            if(head == NULL)
            {
                head = tail = d2;//初始化头尾指针
            }
            else
            {
                tail->next = d2;
                tail = tail->next;//tail指向下一节点
            }
            d2 = d2->next;//d2指向下一节点
        }
        else
        {
            if(head == NULL)
            {
               head = tail = d1;//初始化头尾指针
            }
            else
            {
                tail->next = d1;
                tail = tail->next;//tail指向下一节点
            }
            d1 = d1->next;//d1指向下一节点
        }
    }
    if(d1)
    {
        tail->next = d1;
    }
    else if(d2)
    {
        tail->next = d2;
    }
    return head;
}

方法2

方法1声明head,tail指针之后对于不同情况需要对head,tail赋不同的值,那么有没有一种不需要赋值的方法呢,有,那就是哨兵位的头节点。

哨兵位的头节点就是额外开辟的一个值NULL的新节点guard,d1,d2判断完之后直接与之链接,就不存在赋值操作。理论上来说哨兵位头节点只需要重新定义一个结构体即可,但实际上需要用malloc函数为它开辟空间,并在使用完毕之后释放掉它。

我们在开辟空间的时候,让tail与guard指向同一块空间,这样做既保存了头节点的位置还能确保后续完成链接,只需要让tail指针向后指向即可。

由此可得代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* d1 = list1;
    struct ListNode* d2 = list2;
    struct ListNode* guard = NULL;
    struct ListNode* tail = NULL;
    guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode));//guard与tail指向同一块空间
    tail->next = NULL;//初始化
    while(d1 && d2)
    {
        if(d1->val >= d2->val)
        {
            
            tail->next = d2;
            tail = tail->next;
            d2 = d2->next;
        }
        else
        {
            tail->next = d1;
            tail = tail->next;
            d1 = d1->next;
        }
    }
    if(d1)
    {
        tail->next = d1;
    }
    else if(d2)
    {
        tail->next = d2;
    }
    struct ListNode* head = guard->next;//保存头节点位置
    free(guard);
    guard = NULL;//释放内存,赋值为空
    return head;
}

你可能感兴趣的:(C语言数据结构,链表,数据结构,c语言)