C语言数据结构基础—单链表相关数据结构题目6道

       上一篇博客中,我们大致的讲解了单链表相关的各种接口。接下来我们通过例题来运用类似的思想,达到学以致用的目的。

1.移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

   没有说明头结点是什么,默认就是第一个元素,而不是哨兵位。

思路一:

遍历链表,利用删除节点的操作,遇到节点就删除

 typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    ListNode* pcur=head;
    ListNode* prev=NULL;
    while(pcur){
        if(pcur->val==val){
            //SLTErase(pcur);
            if(prev){
                prev->next=pcur->next;
                free(pcur);
                pcur=prev->next;
            }else{//开头为空的情况
                head=head->next;
                pcur=head;
            }
        }else{
            prev=pcur;
            pcur=pcur->next;
        }
    }
    return head;
}

        整体思路不难,但具体操作有很多细节需要注意,比较繁琐:在遍历时,为了保证能让该节点的前后节点相连,必须要用一个prev比pcur一直慢走一步。同时,如果prev为空时,删除的操作还不一样。

思路二:

使用新的链表指针NewHead和NewTail

注意,此处并没有开辟新的空间,我们只是利用两个指针串联了所有符合要求的节点

将符合条件的5转移过来之后,5的后面看似是NULL,实在5的next指向的是6,需要在最后给ptail指向的节点的next赋值为NULL。又有可能出现空链表的特殊测试用例情况(用例2),所以我们再用if判断一下,保证newTail的next是存在的

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
   ListNode* NewHead;
   ListNode* NewTail;//最终会走到尾巴,但其实际作用是遍历链表
   NewHead=NewTail=NULL;
   ListNode* pcur=head;
   while(pcur){
        if(pcur->val!=val){
            if(NewHead==NULL){//第一次
                NewTail=NewHead=pcur;
            }else{
                NewTail->next=pcur;
                NewTail=pcur;
            }
        }
       pcur=pcur->next;
   }
   if(NewTail){
       NewTail->next=NULL;
   }
   return NewHead;
}

2.链表的中间节点

876. 链表的中间结点 - 力扣(LeetCode)

非常简单的题目(计数一次之后直接找中点即可),但重点不是如何通过该题目,而是学习新思想:快慢指针

先定义两个指针 fast slow

核心思想就是:slow走一步,fast走两步   

不过,任然需要分类考虑偶数和奇数的不同情况:从头开始走,总数为偶数时fast走到末尾的NULL,总数为奇数时fast走到最后一个节点。

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

!!注意:如果调换while里面的两个条件的顺序,将发生 执行错误 ,原因是当有偶数个时,我们最后fast会停在NULL的位置,无法通过NULL找到其next,所以先判断fast如果fast就已经不满足条件了,由于c语言短路的特性,将不再判断fast->next,从而避免出错。

3.反转链表

206. 反转链表 - 力扣(LeetCode)

思路一:遍历原链表结点,并且创建新链表,遍历时每个结点都进行头插到新链表

思路二:使用三个指针n1 n2 n3,并依次赋值NULL  head  head->next

分别记录:前驱节点 当前节点 后继节点

改变节点的指针指向,以此来达到反转的目的。具体流程如下:

     我们可以先直接让n2(节点一)的next指向n1(NULL),并且由于n3保存了节点2的地址所以不用担心丢失节点2,然后

n1=n2;
n2=n3;
n3=n3->next;

此时就变成了:节点一指向NULL,n1指向节点一,n2指向节点二,n3指向节点三,一轮循环结束,已经成功逆置了第一个元素,让他成为next指向NULL的目标链表的最后一个元素 。我们继续这个操作,仍然让n2指向n1,再

n1=n2;
n2=n3;
n3=n3->next;

.....................   依次循环

不过细心的你一定发现,我们再使用n3->next之前最好先判断n3是否为空,免得出现“执行错误”。

n1=n2;
n2=n3;
if(n3)
n3=n3->next;

直到最后一次时

最后一次的特征是:n2=n3;执行后,n2为空,这就成为了我们while循环里的条件。

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

4.合并两个有序链表

21. 合并两个有序链表 - 力扣(LeetCode)

有多种多样的实现思路,比如将l2与l1比较,比较到合适位置就将l2的元素搬运到l1中并插入。

但是为了体现一种新思想:使用 哨兵位

我们此处只着重介绍一种思路:创建新链表

大致思路就是,先创建两个节点newHead newTail 指针,再用l1 l2两个链表指针分别遍历两个链表,分别比较l1 l2的数据,谁小就把谁放进新链表,当任意一个指针指向空(已经跑出了链表时),就结束循环,将指向非空的指针所指向的剩下的内容(最小的一些数据),接到新链表中。

     面对这种ONLINE JUDGE题目,我们不难发现。他会给一些抽象的特殊测试用例,如此题中的case2 3

就像高中时数学大题做不出来就先做特殊情况蹭分数的想法一样,先写出两个特殊情况:

然后完成我们的逻辑

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
   //用两个变量代替,避免最后找不到list1和list2
    ListNode* l1=list1;
    ListNode* l2=list2;

    ListNode* newHead=NULL;
    ListNode* newTail=NULL;

    if(l1==NULL){
        return list2;
    }
    if(l2==NULL){
        return list1;
    }

    while(l1&&l2){
     if(l1->val<l2->val){
        if(newHead==NULL){
            newHead=newTail=l1;
        }else{
              newTail->next=l1;
              newTail=newTail->next;
        }
        l1=l1->next;
    }else{
        if(newHead==NULL){
            newHead=newTail=l2;
        }else{
            newTail->next=l2;
            newTail=newTail->next;
        }
        l2=l2->next;
    }
 }
 //退出循环,此时应该有一个指针指向空,我们将剩下的一个指向的小数据们接在新链表末尾
 if(l1){
     newTail->next=l1;
 }
 if(l2){
     newTail->next=l2;
 }
    return newHead;
}

细心的朋友会发现,此时我们并没有用到我们的哨兵位:

无论l1->val和l2->val的大小,我们都必须先判断newHead和newTail是否为空(两个判断一个即可),冗杂,这就体现哨兵位的优点了:

我们在newHead指向的新链表的第一个元素插入一个哨兵位,里面不放元素或者放置无效元素,避免检查新指针是否为空的这一步,优化代码

也就是将newHead newTail赋值为NULL的两步修改为

 ListNode* newHead=(ListNode*)malloc(sizeof(struct ListNode));
    ListNode* newTail=newHead;

有效减少while中的内容 

最后由于哨兵位(newHead)不再使用,为了避免出函数后内存泄漏,应当执行free(不需要置NULL,因为出函数后该变量自动就销毁了)

5.分割链表

面试题 02.04. 分割链表 - 力扣(LeetCode)

思路一:创建一个新链表,小于x的头插,大于或等于x的尾差

思路二:大小链表法

(大小链表分别定义一个头一个尾,lessHead和greaterHead分别作为哨兵位)

typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x){
   if(head==NULL){
       return head;
   }
   ListNode* pcur=head;
   ListNode* lessTail,*lessHead;
   ListNode* greaterHead,*greaterTail;
   //将哨兵位赋值给大小链表
   lessTail=lessHead=(ListNode*)malloc(sizeof(struct ListNode));
   greaterHead=greaterTail=(ListNode*)malloc(sizeof(struct ListNode));

   while(pcur){
       if(pcur->val>=x){
           greaterTail->next=pcur;
           greaterTail=greaterTail->next;
       }else{
            lessTail->next=pcur;
           lessTail=lessTail->next;
       }
       pcur=pcur->next;
   }
   //链接大小链表,注意给末尾赋NULL
   greaterTail->next=NULL;
   lessTail->next=greaterHead->next;
   

   ListNode* ret=lessHead->next;
   free(lessHead);
   free(greaterHead);
   return ret;
}

注意:当我们把原本不在最后一个位置的节点放在新链表的最后一个节点的时候,一定注意其next是不是指向空,否则就可能出现死循环等错误。

并且链接大小链表的两个句子不能交换,否则greaterTail和greaterHead若为空,就又会发生执行错误。

6.著名的约瑟夫问题

环形链表的约瑟夫问题_牛客题霸_牛客网 (nowcoder.com)

据说著名犹太 Josephus有过以下的故事:在罗⻢⼈占领乔塔帕特后,39 个犹太⼈与
Josephus及他的朋友躲到⼀个洞中,39个犹太⼈决定宁愿死也不要被⼈抓到,于是决定了⼀个⾃杀 ⽅式,41个⼈排成⼀个圆圈,由第1个⼈开始报数,每报数到第3⼈该⼈就必须⾃杀,然后再由下⼀ 个重新报数,直到所有⼈都⾃杀⾝亡为⽌。 然⽽Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与⾃⼰安排在 第16个与第31个位置,于是逃过了这场死亡游戏。
既然是删除中间特定下标的元素,是不是数组(顺序表)更方便使用呢?
当然不是的,中间涉及到删除和成环操作,链表更合适
首先根据n值,创建单向循环链表
ListNode* SLTBuyNode(int x){
    //创建单个的节点
    ListNode* p=(ListNode*)malloc(sizeof(ListNode));
    p->val=x;
    p->next=NULL;
    return p;
 }

 ListNode* SLTCreat(int n){
    ListNode* phead=SLTBuyNode(1);
    ListNode* ptail=phead;
    for(int i=2;i<=n;i++){
        ptail->next=SLTBuyNode(i);
        ptail=ptail->next;
    }
    //让首尾相连
     ptail->next=phead;
     return ptail;//因为后继需要执行删除操作,需要前驱结点prev,
     //所有我们直接返回尾结点来作为prev
 }

注意:这道题貌似没有定义节点的结构体,但是其实已经定义了,输入LIstNode的时候有一样的名字出现

其次:为什么要返回ptail? 先看主程序代码

int ysf(int n, int m ) {
    ListNode* prev=SLTCreat(n);
    ListNode* pcur=prev->next;
//开始遍历
    for(int count=1;pcur->next!=pcur;){
        if(count==m){
            //删除该节点,并且count回到1,做好前后相连工作
            count=1;
            prev->next=pcur->next;
            free(pcur);
            pcur=prev->next;
        }else{
            //向后移动
            count++;
            prev=pcur;
            pcur=pcur->next;
        }
    }
    return pcur->val;
}

正如前文所说,因为需要prev指针来记录前驱节点,在m=1的情况,我们如果返回头结点给phead,正常情况就会给ptail赋值为NULL,那么就无法执行prev->next的语句。

总的来说,为了避免节点指针为空又使用他们找自己的元素,出现执行出错的情况,我们应该合理使用哨兵位(如第4、5题)和循环链表(如此题)的特点

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智能汽车软硬件产品CES展示汽车技术新亮点

智能汽车是汽车产业发展的新趋势&#xff0c;是未来汽车发展的必然方向。智能汽车是指搭载了先进的传感器、控制器、执行器等部件&#xff0c;并融合了人工智能、自动驾驶等技术&#xff0c;能够实现部分或完全自动驾驶、智能网联等功能的汽车。 近年来&#xff0c;智能汽车技…

ensp模拟单臂路由实现不同两个网段主机访问

拓扑结构图如下 1.pc机配置略过 2.交换机配置 三个接口&#xff0c;两个连接pc&#xff0c;连接方式access&#xff0c;一个连接路由器 连接方式trunk sy #进入系统 视图模式 undo info-center enable #关闭信息 vlan batch 10 20#批量创建vlan int g 0/0/2#进入2端口 p…

Java图书管理系统---命令行

项目列表 Book包 Book类内包含book的基本属性 BookList类初始化图书列表并且提供图书的属性方法 User包 Administrator类 common类 operator包 功能接口 新增图书功能 借阅图书功能 删除图书功能 显示图书功能 查找图书功能 归还图书功能 结束释放资源功能 运行…

MySQL进阶:大厂高频面试——各类SQL语句性能调优经验

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;MySQL进阶&#xff1a;强推&#xff0c;冲大厂必精通&#xff01;MySQL索引底层&#xff08;BTree&#xff09;、性能分析、使用…

批量获取图片(下)

1.问题分析 我们观察下之前获得的图片&#xff0c;可以发现图片的清晰度不够。 放大之后比较模糊&#xff0c;这样的图叫做缩略图&#xff0c;那该如何获得高清海报图片呢&#xff1f; 为了找到高清图片&#xff0c;我们需要对网页结构进行分析&#xff0c;找到高清图对应的链…

C#,动态规划(DP)金矿问题(Gold Mine Problem)的算法与源代码

1 金矿问题&#xff08;Gold Mine Problem&#xff09; 给定一个N*M尺寸的金矿&#xff0c;每个点都有一个非负数表示当前点所含的黄金数目&#xff0c;最开始矿工位于第一列&#xff0c;但是可以位于任意行。矿工只能向右&#xff0c;右上&#xff0c;右下三个方向移动。问该…

Eureka 入门教程

Eureka 介绍 1. 注册中心概述 什么是注册中心&#xff1f; 给客户端提供可供调用的服务列表&#xff0c;客户端在进行远程调用&#xff08;RPC&#xff09;时&#xff0c;根据服务列表选择服务提供方的服务地址进行服务调用 注册中心的核心功能 注册&#xff1a;服务提供者上…

CY8C42(1.PSoC4 Pioneer Kit开箱及基本使用)

1.开箱 最近了解到赛普拉斯有一种芯片&#xff0c;属于PSoC系列&#xff0c;与传统MCU不同&#xff0c;有点类似跨界芯片&#xff0c;于是就买来玩玩了&#xff0c;老实说用完还是很特别的&#xff0c;因为我没有用过FPGA&#xff0c;不确定是不是FPGA的开发流程&#xff08;有…

【性能测试】loadrunner12.55--知识准备

1.0. 前言 ​ 在性能测试中&#xff0c;牵扯到了许多比较杂的知识点&#xff0c;这里将给大家说一下&#xff0c;loadrunner性能测试前需要做的一些准备&#xff0c;本节中我们将先从性能测试的一些术语入手&#xff0c;再到HTTP的一些知识&#xff0c;最后导我们loadrunner12…

linux文件及文件内容查找命令总结

在linux环境下&#xff0c;我们经常要查找一个文件或者文件的内容&#xff0c;但搜索的命令有很多&#xff0c;这些命令都有什么区别&#xff0c;应该怎么选择和使用呢&#xff1f; 下面总结了一些常见的文件查找、内容查找的命令&#xff0c;收藏起来备用吧。 文件查找 where…

虚拟机中window7界面太小解决办法

1.在虚拟机中的桌面的空白处右击&#xff0c;然后点击屏幕分辨率 2.根据自己电脑屏幕的大小来选择对应分辨率

java之servlet

动态的web资源开发技术 不同的用户&#xff0c;或者携带不同的参数&#xff0c;访问服务器 服务器添加判断层&#xff0c;实现访问不同的web资源

c++数据结构算法复习基础-- 2 -- 线性表-单链表-常用操作接口-复杂度分析

1、链表 特点 每一个节点都是在堆内存上独立new出来的&#xff0c; 节点内存不连续优点 内存利用率高&#xff0c;不需要大块连续内存 插入和删除节点不需要移动其它节点&#xff0c;时间复杂度O(1)。 不需要专门进行扩容操作缺点 内存占用量大&#xff0c;每一个节点多出存…

LeetCode238题:除自身以外数组的乘积(python3)

代码思路&#xff1a; 当前位置的结果就是它左部分的乘积再乘以它右部分的乘积&#xff0c;因此需要进行两次遍历&#xff0c;第一次遍历求左部分乘积&#xff0c;第二次遍历求右部分的乘积&#xff0c;再将最后的计算结果一起求出来。 class Solution:def productExceptSelf(…

【力扣 - 杨辉三角】

题目描述 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1]] 提示: 1 < numRows < 30 方法一&#xff1a;数学 思路…

【免费】两阶段鲁棒优化matlab实现——CCG和benders

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序采用matlab复现经典论文《Solving two-stage robust optimization problems using a column-and-constraint generation method》算例&#xff0c;实现了C&CG和benders算法两部分内容&#xff0c;通过…

93. 递归实现组合型枚举 刷题笔记

与我前面发的递归实现那一题有点相似 可以看看 94. 递归实现排列型枚举 刷题笔记-CSDN博客 思路 用u记录 选到哪一个位置 一旦选完 就输出 该题 要求升序 我们在选数时加入一个条件 大于上一个选择的数即可 依旧是从小到大搜到符合条件的每一个数 代码 #include<…

安防视频监控EasyCVR平台使用GB28181协议接入时,如何正确配置端口?

国标GB28181协议EasyCVR安防视频监控平台可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;平台支持7*24小时实时高清视频监控&#xff0c;能同时播放多路监控视频流…

多线程万字详解

进程和线程是计算机程序执行的两个重要概念。 1.进程&#xff1a; 进程是操作系统分配资源的基本单位&#xff0c;每个进程都有自己独立的地址空间&#xff0c;每启动一个进程&#xff0c;系统就会为它分配内存。进程间通信比较复杂&#xff0c;需要用到IPC&#xff08;InterP…

Day07:基础入门-抓包技术全局协议封包监听网卡模式APP小程序PC应用

目录 非HTTP/HTTPS协议抓包工具 WireShark 科来网络分析系统 WPE封包 思维导图 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令&#xff1a;文件上传下载/端口服务/Sh…