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

news2024/11/27 8:26:27

目录

  • 1. 移除链表元素
  • 2. 反转链表
    • 2.1反转指针法
    • 2.2 头插法
  • 3. 合并两个有序链表
  • 4. 分隔链表
  • 5. 环形链表
  • 6. 链表的中间节点
  • 7. 链表中倒数第K个节点
  • 8. 相交链表
  • 9. 环形链表的约瑟夫问题
  • 10. 链表的回文结构

1. 移除链表元素

在这里插入图片描述
思路1:遍历原链表,将 val 所在的节点释放掉。(太麻烦)

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

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/342214109d444655a6081115428b345c.png

思路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;
 
    }
 };

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1599715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

YOLOv8最新改进系列:融合DySample超轻量动态上采样算子,低延迟、高性能,目前最新上采样方法!!!遥遥领先!

YOLOv8最新改进系列&#xff1a;融合DySample超轻量动态上采样算子&#xff0c;低延迟、高性能&#xff0c;目前最新上采样方法&#xff01;&#xff01;&#xff01;遥遥领先&#xff01; DySample超轻量动态上采样算子全文戳这&#xff01;here! 详细的改进教程以及源码&am…

复习回顾ES6基础篇(一小时学会es6)

基本语法 多行注释 /* 这里的所有内容 都是注释。 */单行注释 // 这是一条注释。变量定义 var x "" //定义范围变量 let y "" //定义局部变量 const z "" //定义常量运算符 变量类型 流程语句 if (condition) {/* 条件为真时运行的代…

《中医病证分类与代码》-中医疾病分类数据库

《中医病症分类与代码》由国家中医药管理局2020年底修订&#xff0c;目的是为中医疾病及证候的分类提供统一的规范。规定2021年起&#xff0c;各中医机构的临床科室及基层中医药的医师都应按照最新修订的《中医病症分类与代码》规范来填报病案及病历。 中医病证分类与代码数据库…

js base64 img 转 file

转化canvas的图像 打印canvas图像的dataurl const imgDataUrl canvas.toDataURL();console.log(imgDataUrl)上传的图像编码 1,把base64编码转为文件对象第一个参数dataUrl是一个base64的字符串。第二个参数是文件名可以随意命名funtion base64toFile(dataurl, filename fil…

基于ADB的Scrcpy实现电脑控制手机

Scrcpy是一个开源的&#xff0c;基于ADB&#xff08;Android 调试桥&#xff09;的手机到电脑上的投屏操控的实现&#xff0c;本文将介绍如何搭建开发环境&#xff0c;使得在Windows系统中去控制投屏的安卓手机。 1. 安装投屏软件 下载Scrcpy软件到电脑上&#xff0c;该软件中…

推荐一个超好用的测试工具,值得体验!

在软件开发领域中&#xff0c;测试是确保质量与可靠性的必要环节。俗话说得好“工欲善其事&#xff0c;必先利其器”&#xff0c;测试工具越简单、用户友好度越高&#xff0c;开发者编写测试的意愿度就越高。 为了满足大家的测试需求&#xff0c;MoonBit 标准库最近引入了 ins…

基于Python的景区票务人脸识别系统(V2.0)

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

安装SSMS出现错误和SSMS连接数据库失败

1.点击图片下安装的时候&#xff0c;出现0x80070643错误 解决办法&#xff1a; 1,如果是是第一次安装的话&#xff0c;重新启动电脑&#xff0c;把原来下载的SSMS删除掉&#xff0c;在重新下载安装 2.如果是原来就下载过&#xff0c;先…

基于Docker构建CI/CD工具链(七)使用Jmeter进行自动化压测

上一篇文章中&#xff0c;我们详细介绍了构建 Apifox Cli 的 Docker 镜像的步骤&#xff0c;并通过简单的示例演示了如何利用 GitLab 的 CI/CD 功能&#xff0c;将构建好的镜像利用在自动化测试作业中。在今天的文章中&#xff0c;我们将重点讨论如何构建 JMeter 的 Docker 镜像…

彻底解决 pyshark 库 TShark not found

使用 python 运行 github 某个项目处理 pcap 包时遇到如下报错&#xff1a; &#xff08;先安装了 pyshark 库&#xff09; pyshark.tshark.tshark.TSharkNotFoundException: TShark not found. Try adding its location to the configuration file. Searched these paths: […

现在谷歌企业号需要验证企业官网和法人信息,才能注册成功或提审应用?

众所周知&#xff0c;近年来&#xff0c;随着谷歌上架行业的发展&#xff0c;以及开发者们上架马甲包或矩阵式上架的操作&#xff0c;谷歌官方对于开发者账号的审核越来越严格了。 从一开始需要提供收付款卡银行流水账单&#xff0c;到后续引入邓白氏码等更为严格的账号验证机制…

【学习笔记】Vue3源码解析:第四部分- runtime-dom(1)

课程地址&#xff1a;【已完结】全网最详细Vue3源码解析&#xff01;&#xff08;一行行带你手写Vue3源码&#xff09; 第四部分-&#xff1a;&#xff08;对应课程的第24-26节&#xff09; 第24节&#xff1a;《理解runtime-dom的作用》 源码中除了 dep.ts &#xff0c;其余基…

00_Qt概述以及如何创建一个QT新项目

Qt概述 1.Qt概述1.1 什么是Qt1.2 Qt的发展史1.3 支持的平台1.4 Qt版本1.5 Qt的下载与安装1.6 Qt的优点 2.QT新项目创建3.pro文件4.主函数5.代码命名规范和快捷键 1.Qt概述 1.1 什么是Qt Qt是一个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面…

边缘计算网关主要有哪些功能?-天拓四方

随着物联网&#xff08;IoT&#xff09;的快速发展和普及&#xff0c;边缘计算网关已经成为了数据处理和传输的重要枢纽。作为一种集成数据采集、协议转换、数据处理、数据聚合和远程控制等多种功能的设备&#xff0c;边缘计算网关在降低网络延迟、提高数据处理效率以及减轻云数…

loD:如何实现代码的“高内聚、低耦合“

设计模式专栏&#xff1a;http://t.csdnimg.cn/3a25S 目录 1.引用 2.何为"高内聚、低耦合" 3.LoD 的定义描述 4.定义解读与代码示例一 5.定义解读与代码示例二 1.引用 本节介绍最后一个设计原则:LoD(Law of Demeter&#xff0c;迪米特法则)。尽LoD不像SOLID、KI…

面试:lock 和 synchronized

一、语法层面 synchronized 是关键字&#xff0c;源码在jvm中&#xff0c;用c语言实现Lock 是接口&#xff0c;源码由jdk提供&#xff0c;用java语言实现使用synchronized时&#xff0c;退出同步代码块锁会自动释放&#xff0c;而使用Lock时&#xff0c;需要手动调用unlock方法…

Linux中进程和计划任务

一.程序 1.什么是程序 &#xff08;1&#xff09;是一组计算机能识别和执行的指令&#xff0c;运行于电子计算机上&#xff0c;满足人们某种需求的信息化工具 &#xff08;2&#xff09;用于描述进程要完成的功能&#xff0c;是控制进程执行的指令集 二.进程 1.什么是进程…

量化过程信息损耗分析(MATLAB)

MATLAB代码 clear_all; Mrand(5,5)*100;% 假设M是待转换的矩阵 a min(M(:)); b max(M(:));% 将M映射到[0, 255] M_mapped functionA(M, a, b); M_mapped_floorfloor(M_mapped); % 将M_mapped恢复到原始范围 M_original functionB(M_mapped_floor, a, b);disp(M); disp(M_m…

【吊打面试官系列】Java高并发篇 - 什么是多线程中的上下文切换?

大家好&#xff0c;我是锋哥。今天分享关于 【什么是多线程中的上下文切换&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是多线程中的上下文切换&#xff1f; 在上下文切换过程中&#xff0c;CPU 会停止处理当前运行的程序&#xff0c;并保存当前程序运行…

【Altium Designer 20 笔记】PCB铺铜过程

PCB铺铜步骤 切换到Keep-Out Layer&#xff08;禁止布线层&#xff09; 使用shifts键切换单层显示 画禁止布线范围&#xff08;防止铺铜过大&#xff09; 切换到需要铺铜的层 选择铺铜网络&#xff0c;通常是地&#xff08;GND&#xff09;或某个电源网络 隐藏覆铜&#xff1a;…