【C/数据结构与算法】:10道链表经典OJ

简介: 【C/数据结构与算法】:10道链表经典OJ

1. 移除链表元素

思路1:遍历原链表,将 val 所在的节点释放掉。(太麻烦)

思路2:创建新链表,再遍历原链表,找到不为 val 的节点尾插到新链表。

思路1代码实现如下:

注意:

1.当链表为空时,直接返回NULL即可。

2.当尾插上最后一个有效节点时,此时它的 next 可能还与最后一个节点相链接,一定要断开!

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    if (head == NULL)
        return NULL;
    //创建一个新链表
    ListNode* newHead, * newTail;
    newHead = newTail = NULL;
    ListNode* pcur = head;
    //遍历原链表,找不为val的节点尾插
    while (pcur)
    {
        ListNode* next = pcur->next;
        if (pcur->val != val)
        {
            //没有一个节点
            if (newHead == NULL)
            {
                newHead = newTail = pcur;
            }
            else
            {
                //有一个节点以上
                newTail->next = pcur;
                newTail = newTail->next;
            }
        }
        pcur = next;
    }
    if (newTail)
        newTail->next = NULL;
    return newHead;
}

2. 反转链表

2.1反转指针法

思路:定义三个变量 n1,n2,n3,根据它们的指向关系进行迭代。

代码实现如下:

注意:

1.当链表为空时,直接返回NULL即可。

2.在迭代过程中别忘记判断 n3 ,防止对空指针解引用。

3.注意循环结束的条件,当 n2 为空时,n1 指向反转后的头,此时循环结束。

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL)
        return NULL;
    ListNode* n1, * n2, * n3;
    n1 = NULL, n2 = head, n3 = n2->next;
    while (n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if (n3)
            n3 = n3->next;
    }
    return n1;
}

2.2 头插法

思路:创建一个新链表,遍历原链表,依次取下原链表的每一个节点头插到新链表中。

代码实现如下:

注意:

1.当链表为空时,直接返回NULL即可。

2.头插时可以不用判断没有节点和有节点的情况。

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL)
        return NULL;
    ListNode* newHead, * newTail;
    newHead = newTail = NULL;
    ListNode* pcur = head;
    //一个一个拿下来头插
    while (pcur)
    {
        ListNode* next = pcur->next;
        pcur->next = newHead;
        newHead = pcur;
        pcur = next;
    }
    return newHead;
}

3. 合并两个有序链表

思路:创建一个带哨兵位的新链表,遍历两个原链表,比较两个节点的值,哪个小就先尾插到新链表中。

代码实现如下:

注意:

1.当其中一个链表为空时,返回另一个链表即可。

2.创建哨兵位节点,方便尾插。

3.注意循环结束条件,当其中有一个链表走完时,跳出循环。

4.剩下的没走完的那个链表直接链接到后面。不需要用循环链接,因为它们本来就是连在一起的。

5.别忘记释放释放哨兵位节点,释放前要保存下一个节点。

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    ListNode* n1, * n2;
    n1 = list1;
    n2 = list2;
    if (n1 == NULL)
        return n2;
    if (n2 == NULL)
        return n1;
    //创建哨兵位节点
    ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
    ListNode* newHead, * newTail;
    newHead = newTail = phead;
    while (n1 && n2)
    {
        //比较两个链表中数据的大小,谁小就先尾插到新链表
        if (n1->val < n2->val)
        {
            newTail->next = n1;
            n1 = n1->next;
            newTail = newTail->next;
        }
        else
        {
            newTail->next = n2;
            n2 = n2->next;
            newTail = newTail->next;
        }
    }
    if (n1)
        newTail->next = n1;
    if (n2)
        newTail->next = n2;
    ListNode* ret = newHead->next;
    free(newHead);
    return ret;
}

4. 分隔链表

思路:创建两个带哨兵位的新链表 less 和 greater 。遍历原链表,把小于x 的节点尾插到 less 链表中,把大于等于x 的节点尾插到greater 链表中。最后把 less 链表中的尾结点的 next 链接到 greater链表中的头结点上。

代码实现如下:

注意:

1.当链表尾空时,直接返回NULL即可。

2.有可能存在greater 链表中的最后一个结点与原链表中的最后一个结点仍然相链接的情况,一定要断开!

typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x) {
    if (head == NULL)
        return NULL;
    ListNode* lessHead, * lessTail, * greaterHead, * greaterTail;
    ListNode* pcur = head;
    //创建哨兵位节点,方便尾插
    lessHead = lessTail = (ListNode*)malloc(sizeof(ListNode));
    greaterHead = greaterTail = (ListNode*)malloc(sizeof(ListNode));
    lessTail->next = NULL;
    greaterTail->next = NULL;
    while (pcur)
    {
        if (pcur->val < x)
        {
            lessTail->next = pcur;
            lessTail = lessTail->next;
        }
        else
        {
            greaterTail->next = pcur;
            greaterTail = greaterTail->next;
        }
        pcur = pcur->next;
    }
    lessTail->next = greaterHead->next;
    greaterTail->next = NULL;
    return lessHead->next;
}

5. 环形链表

这是一个非常经典的例题,它要用上一种非常经典的算法:快慢指针法

定义一个 slow 变量,fast 变量,从链表的头结点开始,slow 每次走一步,fast 每次走两步,当 slow 进入环中时,fast 开始追逐。若成环,则必会在环内的某处相遇,否则 fast 或是 fast->next 最后会走到NULL。

代码实现如下:

注意:

1.当链表节点个数为偶数个时,若不成环,最终 fast == NULL。

当链表节点个数为奇数个时,若不成环,最终 fast->next == NULL.

2.循环条件 fast && fast->next 的位置不能交换。因为当为偶数个节点,fast走到NULL时,如果是 fast->next && fast ,那就是先执行 fast->next ,对空指针解引用,错误!!

typedef struct ListNode ListNode;
bool hasCycle(struct ListNode* head) {
    ListNode* slow, * fast;
    slow = fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (fast == slow)
            return true;
    }
    return false;
}

6. 链表的中间节点

也要用快慢指针法。也要分两种情况:

代码实现如下:

注意:

循环条件 fast && fast->next 的位置不能交换。因为当为偶数个节点,fast走到NULL时,如果是 fast->next && fast ,那就是先执行 fast->next ,对空指针解引用,错误!!

typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* slow = head;
    ListNode* fast = head;
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

7. 链表中倒数第K个节点

思路:

(1) fast 先走 k 步;

(2) slow 和 fast 再一起走,当 fast == NULL 时,slow 就是倒数第 k 个节点。

代码实现如下:

注意:

当 k 大于链表长度时,fast 会走到 NULL ,不能对空指针解引用,直接返回 NULL。

typedef struct ListNode ListNode;
struct ListNode* FindKthToTail(struct ListNode* pHead, int k ) {
    ListNode* fast ,*slow;
    fast = slow = pHead;
 
    //fast先走K步
    while(k--)
    {
        //K大于链表长度时,直接返回NULL
        if(fast == NULL)
        {
            return NULL;
        }
         
        fast = fast->next;
    }
 
    //再两者一起走
    while(fast)
    {
        fast = fast->next;
        slow = slow->next;
    }
 
    return slow;
    
 }

8. 相交链表

首先要明确什么是相交链表:

思路1:暴力求解,时间复杂度O(N*N)

依次取A链表中的每个节点跟B链表中的所有节点比较。如果有相同地址的节点,则相交,第一次相同地址的节点就是交点,否则就不相交。

思路2:时间复杂度O(N)

(1) 先找到两个链表的尾,同时计算出两个链表的长度;

(2) 求出长度差;

(3) 判断哪个是长链表;

(4) 让长链表先走长度差步;

(5) 最后长短链表一起走,直到找到交点。

思路2代码实现如下:

注意:

要注意步骤(3)中判断长短链表的巧妙方法。可以避免写重复代码。

typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    
    ListNode* TailA,*TailB;
    TailA = headA;
    TailB = headB;
    //找尾同时计算长度
    int lenA = 0;
    int lenB = 0;
    while(TailA->next)
    {
        TailA = TailA->next;
        lenA++;
    }
     while(TailB->next)
    {
        TailB = TailB->next;
        lenB++;
    }
    //不相交
    if(TailA != TailB)
    {
        return NULL;
    }
    //求出长度差
    int gap = abs(lenA - lenB);
    //判断哪个是长链表
    ListNode* longList = headA;//先默认A是长链表
    ListNode* shortList = headB;
    if(lenA < lenB)
    {
        shortList = headA;
        longList = headB;
    }
    //让长链表走长度差步
    while(gap--)
    {
        longList = longList->next;
    }
    //最后长短链表一起走,找交点
    while(longList != shortList)
    {
        longList = longList->next;
        shortList = shortList->next;
    }
    return longList;
}

9. 环形链表的约瑟夫问题

思路:

首先要创建一个循环链表,定义一个计数器 count 用于数数,再遍历循环链表,当结点的 val == count 时,就"杀死",即销毁该节点。

代码实现如下:

注意:

要学习创建循环链表的方法!

typedef struct ListNode ListNode;
   
  //创建节点
  ListNode* BuyNode(int x)
  {
    ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
    if(newnode == NULL)
    {
        exit(-1);
    }
 
    newnode->val = x;
    newnode->next = NULL;
 
    return newnode;
  }
 
 //1.创建循环链表
 ListNode* Createnodecircle(int n)
 {
    ListNode* phead = BuyNode(1);
    ListNode* ptail = phead;
    for(int i=2;i<=n;i++)
    {
        ptail->next = BuyNode(i);
        ptail = ptail->next;
    }
    //尾连头,成环
    ptail->next = phead;
 
    return ptail;
 
 }
 
int ysf(int n, int m ) {
   
     ListNode* prev = Createnodecircle(n);
     ListNode* pcur = prev->next;
     
     //开始数数
     int count = 1;
     while(pcur->next!=prev->next)
     {
         if(count == m)
         {
            prev->next = pcur->next;
            free(pcur);
            pcur = prev->next;
            count = 1;
         }
         else
         {
         prev = pcur;
         pcur = pcur->next;
         count++;
         }
    }
 
         return pcur->val;
     }

10. 链表的回文结构

这个题在牛客网中不能用C语言实现,我们可以选C++,因为C++是兼容C的,在C++中可以使用C的语法。

思路:

(1) 找到链表的中间节点;

(2) 把中间节点后面的链表进行逆置;

(3) 最后把逆置后的链表的值与中间节点之前的链表的值进行比较,若所有节点相等,则是回文链表,否则不是。有一个链表结束则比较结束。

代码实现如下:

注意:

逆置完之后,中间节点与前一个结点的链接可以不用断开,因为就算链接在一起也不影响判断。若强行断开,徒增麻烦。

//找中间结点
struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* slow = head;
   struct ListNode* fast = head;
 
    while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
 
    return slow;
 
}
 
 //对中间结点之后的链表进行逆置
struct ListNode* reverseList(struct ListNode* head) {
 
    if (head == NULL)
        return NULL;
 
   struct ListNode* n1, * n2, * n3;
    n1 = NULL, n2 = head, n3 = n2->next;
 
    while (n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
 
        if (n3)
            n3 = n3->next;
    }
 
    return n1;
}
  
class PalindromeList {
  public:
    bool chkPalindrome(ListNode* A) {
        
    struct ListNode* mid = middleNode(A);
    struct ListNode* rHead = reverseList(mid);
     
    struct ListNode* curA = A;
    struct ListNode* curR = rHead;
 
    //把逆置后的链表与中间结点之前的链表进行比较
    while(curA && curR)
    {
         if(curA->val != curR->val)
         {
            return false;
         }
         else
         {
            curA = curA->next;
            curR = curR->next;
         }
    }
    return true;
 
    }
 };
目录
相关文章
|
25天前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
51 4
|
18天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
40 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
67 4
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
25天前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
42 0
|
1月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
48 0
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
91 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
38 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
2月前
|
搜索推荐 算法
数据结构与算法学习十四:常用排序算法总结和对比
关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
25 0
数据结构与算法学习十四:常用排序算法总结和对比