链表经典面试题【典中典】

news2025/8/6 3:51:38

💯💯💯链表经典面试题❗❗❗炒鸡经典,本篇带有图文解析,建议动手刷几遍。

  • 🟥1.反转链表
  • 🟧2.合并两个有序链表
  • 🟨3.链表分割
  • 🟩4.链表的回文结构
  • 🟦5.相交链表

🟥1.反转链表

在这里插入图片描述
方法一:利用双指针逆转方向
将各个结点的连接方向都倒过来,2结点指向1结点,3结点指向2结点等,我们只要将让每个结点的next指向前一个结点的地址

双指针–将指针指向前面,最后一个变成头。两个指针,一前一后
注意点:

  • 单链表不能往前走
  • 第一个指针首先指向NULL ,第二个指针在后面,这两个指针用来转变指向
    不过这时第二个指针的后面的地址无法知道,这时需要第三个指针用来记录第二个指针后面的结点的地址。
  • 当第三个指针往后走的时候可能为NULL,要注意下
    在这里插入图片描述

代码如下:

struct ListNode* reverseList(struct ListNode* head)
{
   if(head==NULL)//如果链表为空则直接返回NULL
    return NULL;
   struct ListNode* prev = NULL;//与cur搭配,记录前面的,一开始为NULL
    struct ListNode* cur = head;//一开始为第一个结点,所以将head传给cur,让cur遍历链表
    struct ListNode* next =cur->next;//next用来记录cur后面结点的地址
   
    while (cur)//当cur不为NULL时进行
    {
        //方向逆转
        cur->next = prev;//将每一个结点的next指向前一个结点的地址
        //迭代--让我们的条件往后执行
        prev = cur;//让pre跑到cur的位置上来
        cur = next;//让cur跑到next的位置上来
        if (next)//这里next可能会跑着跑着为NULL了,所以需要判断下
            next = next->next;//往后跑
    }
    return prev;//最后尾就是头了,将尾传回去
}

方法2:利用头插原理
取原结点头插到新链表
原本是1 2 3 4 5
我们只要将每个结点拿下来,然后头插到一个新链表上就会变成5 4 3 2 1了
在这里插入图片描述

代码如下:

struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* cur = head;//利用cur进行遍历,不要用形参head
	struct ListNode* newnode = NULL;//首先创建一个新的空链表
	while (cur)//当cur为空时结束
	{
		//首先要记录下来cur后面结点的地址
		struct ListNode* next = cur->next;
		//头插
		cur->next = newnode;//将取下来的结点头插到新链表中
		newnode = cur;//更新头指针
		cur = next;//cur要往后走
	}
	return newnode;//返回新链表的头指针
}

🟧2.合并两个有序链表

在这里插入图片描述
这种题目在数组中也有类似的,原理也是一样,合并两个有序数组
也就是依次比较取小的结点尾插,最后如果比较完还有结点直接尾插到没有结点的后面去。

注意点:

  • 当第一次尾插到一个新链表时,我们需要的 是进行直接赋值,将第一个结点直接赋值给新链表的头指针而不能进行尾

  • 当第一次以后才可以进行真正的尾插

  • 当其中有一个或两个链表为空链表的话,那么直接跳过比较环节,而进入将还有结点的直接尾插到没有结点的后面去
    这时因为没有结点的链表为NULL,所以就无法访问它的next也就无法将它和另一个链表链接起来。

    所以这时我们需要在前面讨论一下,当其中有一个链表为NULL,则直接返回另外一个链表。

方法1:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
	if (list1 == NULL)//当链表1为空则直接返回链表2
		return list2;
	if (list2 == NULL)//当链表2为空则直接返回链表1
		return list1;

	struct ListNode* cur1 = list1, * cur2 = list2;//利用cur1,cur2遍历链表
	struct ListNode* head = NULL, * tail = NULL;//head,用来传回,tail用来找尾
	while (cur1 && cur2)//当其中有一个结束就结束
	{
		if (cur1->val < cur2->val)//哪个小就尾插谁
		{
			//将小的尾插
			//但一开始为空第一步就是赋值
			if (head == NULL)
			{
				head = tail = cur1;
			}
			//接下来才是真正的尾插
			else
            {
                tail->next = cur1;//将cur1链接在tail的后面
			    tail = tail->next;//tail要往后找尾,这样就不需要每次从开始进行找尾了
            }
			cur1 = cur1->next;//cur往后走
		}
		else 
		{
			//将小的尾插
			//但一开始为空第一步就是赋值
			if (head == NULL)
			{
				head = tail = cur2;
			}
			//接下来才是真正的尾插
			else
            {
                tail->next = cur2;
			    tail = tail->next;
            }
			cur2 = cur2->next;
		}
	}
	//将长的链表指针尾插到tail后面
	//不过还有一种情况就是plist为空cur1为空,则tail还是NULL,这种情况需要讨论
	if (cur1)
	{
		tail->next = cur1;
	}
	else
	{
		tail->next = cur2;
	}
	return head;//返回head
}

在这里插入图片描述
在这里插入图片描述
还有一种方法可以避免讨论tail的next是否为空,和第一次尾插需要赋值等问题。
我们可以利用带有头哨兵卫的头结点。
这样tail永远不可能为空了,就不需要讨论那么多为空的情况了。

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode* cur1 = list1, * cur2 = list2;
	struct ListNode* head =NULL,* tail = NULL;
	struct ListNode* guard = tail = (struct ListNode*)malloc(sizeof(struct ListNode));//动态开辟一个哨兵头结点,让tail一开始就指向头结点,这样tail就永远不会为NULL了
	while (cur1 && cur2)//当其中有i一个结束就结束
	{
		if (cur1->val < cur2->val)
		{
			//将小的尾插
			//不需要进行讨论第一个头节点为NULL的情况了,直接进行尾插
				tail->next = cur1;
				tail = tail->next;
			cur1 = cur1->next;
		}
		else
		{
			//将小的尾插
				tail->next = cur2;
				tail = tail->next;
			    cur2 = cur2->next;
		}
	}
	//将长的链表指针尾插到tail后面
	//如果没有哨兵头,plist为空cur1为空,则tail还是NULL,这种情况就需要讨论
	//但先走有了哨兵头结点,tail不可能为空,直接让tail的next与另一个链表剩余的结点链接起来
	if (cur1)
	{
		tail->next = cur1;
	}
	else
	{
		tail->next = cur2;
	}
    head=guard->next;//将第一个结点的地址记录下来
    free(guard);//释放guard
	return head;
}

在这里插入图片描述

🟨3.链表分割

在这里插入图片描述
要求将所有小于x的结点排在其他结点之前,且不能改变原来的数据顺序
我们可以这样做:

  1. 将小于x的结点插入到一个链表中
  2. 将大于x的结点插入到一个链表中
  3. 最后将两个链表链接起来。

在这里插入图片描述
不过这里我们最好使用带有哨兵头的链表,这样可以减少进行对NULL的讨论不然会很麻烦
比如如果数据全是 6 6 6 6 6 而x为5
则less链表为空,那么将两个链表链接起来有会出问题,ltail都为NULL还有next吗?,所以我们使用带有哨兵卫的链表能很好的减少这种讨论。
在这里插入图片描述
在这里插入图片描述

class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        ListNode* Lguard,*Gguard,*ltail,*gtail;
        Lguard=ltail=(ListNode*)malloc(sizeof(ListNode));//less链表的哨兵头
        Gguard=gtail=(ListNode*)malloc(sizeof(ListNode));//greater链表的哨兵头
        ltail->next=NULL;//一开始要置NULL
        gtail->next=NULL;
        ListNode* cur=pHead;//利用cur进行遍历
        while(cur)
        {
            if(cur->val<x)//如果小于x就尾插到less的链表中
            {
                ltail->next=cur;//将小的数据尾插到ltail的后面
                ltail=ltail->next;//找尾
            }
            else {
            gtail->next=cur;//将大的数据尾插到gtail后面
            gtail=gtail->next;
            }
            cur=cur->next;//每次cur也要往后走
        }
        ltail->next=Gguard->next;//让两个链表链接起来
        gtail->next=NULL;//想一想这一步是为什么呢?因为最后7的next还链接着2呀,这样就造成回环了。
        所以需要将gtail的next置为NULL
        pHead=Lguard->next;//第一个结点
        free(Lguard);//释放
        free(Gguard);//释放
        return pHead;//返回第一个结点地址
        
    }
};

在这里插入图片描述

🟩4.链表的回文结构

在这里插入图片描述
我们看到链表时有时就会想转空子,想先使用数组来存储链表的数据,然后再进行判断,我们在知道链表长度的前提下理论上是可以的,但最好不要这样做,如果不知道长度,我们还需要动态增容等,所以要抛弃这个想法。
那怎么判断回文结构呢?
回文结构也就是对称,从中间对称,左边与右边对称,如果我们把右边逆转下,那么右边就和左边一样了。所以我们就可以根据左边和右边是否一样进行判断了

  1. 首先我们需要找到中间结点
  2. 逆转中间结点以后的结点
  3. 从逆转开始的位置与开头进行比较

逆转链表,查找链表的中间结点我都已经写过了,这里直接就复制过来。
《找链表的中间结点》
《反转链表》
在这里插入图片描述

class PalindromeList {
public:
struct ListNode* middleNode(struct ListNode* head)//找链表中间结点
{
   struct ListNode *fast,*slow;
   fast=slow=head;
   while(fast!=NULL&&fast->next!=NULL)
   {
       slow=slow->next;
       fast=fast->next->next;
   }
   return slow;

}
struct ListNode* reverseList(struct ListNode* head)//反转链表
{
struct ListNode* cur = head;
	struct ListNode* newnode = NULL;
	while (cur)
	{
		//首先要记录下来cur后面结点的地址
		struct ListNode* next = cur->next;
		//头插
		cur->next = newnode;
		newnode = cur;//更新头指针
		cur = next;//cur要往后走
	}
	return newnode;
}
    bool chkPalindrome(ListNode* phead)
     {

      ListNode *mid=  middleNode(phead);//找中间结点
      ListNode *rhead = reverseList(mid);//逆转中间结点以后
      //比较链表前面数据与逆转后的数据
      while(rhead)//当逆转后的结点走到NULL时结束
      {
        if(rhead->val!=phead->val)
           return false;//当有一个不等于就然后false
          phead=phead->next;
          rhead=rhead->next;
      }
      return true;//走到这说明都相等
    }
};

在这里插入图片描述

🟦5.相交链表

在这里插入图片描述
怎么判断两个链表是否相交呢?
怎么返回这个相交结点呢?

传统思想可能会让链表A中每个结点的地址与链表B中结点的地址都比较一下,当第一次比较相同时,就是相交结点,不过这种方法的时间复杂度为O(N^2)了,不好,有没有让时间复杂度为O(N)的呢?

好的让我来告诉你吧:
如果两个链表是相交的,那么它们的尾结点的地址一定相同,如果尾结点地址不相同那么肯定不相交。
接着让长的链表先走长度差步,然后两个链表再一起走,当两个链表结点地址比较相同时就是相同结点的位置。
所以

  1. 求出两个链表的长度,判断是否相交。
  2. 让长度长的链表先走长度差步,然后一起走
  3. 两个链表结点地址相比,第一次相同的为相交结点
    在这里插入图片描述
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
      struct ListNode *cur1=headA,*cur2=headB;//用cur1遍历链表A,用cur2遍历链表2
    int lena=0;
    int lenb=0;
    while(cur1->next)//记录链表A的长度
    {
        cur1=cur1->next;
        ++lena;
    }
    while(cur2->next)//记录链表B的长度
    {
        cur2=cur2->next;
        ++lenb;
    }
    if(cur1!=cur2)//如果链表尾结点不相同那么肯定不相交
    return NULL;
    int gap=abs(lena-lenb);//长度差,但我们不知道哪个链表长
    struct ListNode *longlist=headA,*shortlist=headB;
    if(lenb>lena)//所以我们需要讨论下
    {
     longlist=headB,shortlist=headA;
    }
    while(gap--)//让长的链表先走长度差步
    {
        longlist=longlist->next;
    }
    while(longlist!=shortlist)//然后两个链表一起走,当没有结点地址相同时则一起走
    {
        longlist=longlist->next;
        shortlist=shortlist->next;
    }
    return longlist;//最后如果有相同的就返回
}

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

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

相关文章

74. ‘pip‘不是内部或外部命令,也不是可运行的程序-解决办法

74. pip’不是内部或外部命令&#xff0c;也不是可运行的程序-解决办法 文章目录74. pip不是内部或外部命令&#xff0c;也不是可运行的程序-解决办法1. 课题导入2. 手动配置环境变量1. 准备工作2. 配置步骤3. 命令行安装1. 课题导入 有的同学在使用pip安装第三方库时&#xf…

elasticsearch 分布式搜索引擎1

1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在GitHub搜索代码 在电商网站搜索商品 在百度搜索答案 在…

WorksPace一款简化修改IP繁琐重复性工作的高效工具

WorksPace简介 workspace软件中文版是一个集成修改网卡IP、网络扫描、记事本、快速启动的效率工具&#xff0c;前端使用Flutter&#xff0c;后端主要是Dart&#xff0c;少量的后台功能用以前Golang&#xff0c;数据存储使用SQLite。主要是为了简化我在日常工作中的频繁重复操作…

app里未读消息已读、未读是怎么设计的?

也不知道大家目前都用的java编程软件有哪些&#xff0c;毕竟在应用程序中&#xff0c;未读和已读消息的设计取决于应用程序的需求和目标。下面是一些常见的设计模式&#xff1a;一、简单的未读/已读标记简单的未读/已读标记&#xff1a;这是最常见的设计&#xff0c;用户打开应…

Linux - Linux系统优化思路

文章目录影响Linux性能的因素CPU内存磁盘I/O性能网络宽带操作系统相关资源系统安装优化内核参数优化文件系统优化应用程序软件资源系统性能分析工具vmstat命令iostat命令sar命令系统性能分析标准小结影响Linux性能的因素 CPU CPU是操作系统稳定运行的根本&#xff0c;CPU的速…

一元导数与多元求导数总结

前序&#xff1a;文章结构 1.一元导数 ①一般函数求导 因为太简单的原因&#xff0c;事实上一般函数求导不会单独出现&#xff0c;大多数都是出现在各种特殊的求导过程中。只要掌握16个基本求导公式没问题。 ②复合函数求导&#xff08;主要链式法则&#xff09; 这种一般是…

流量监管与整形

流量监管与整形概览流量监管介绍流量监管令牌桶流量监管的具体实现单桶单速流量监管双桶单速流量监管双桶双速流量监管流量整形介绍GTS&#xff08;Generic Traffic Shaping&#xff09;LR&#xff08;Line Rate&#xff09;流量整形与流量监管的区别概览 流量整形是对报文的速…

某美颜app sig参数分析

之前转载过该app的文章&#xff0c;今天翻版重新整理下&#xff0c;版本号:576O5Zu56eA56eAYXBwIHY5MDgw (base64 解码)。 上来先抓个包&#xff1a; jadx搜索关键词 "sigTime"&#xff0c;然后定位到这里 看这行代码 cVar.addForm(INoCaptchaComponent.sig, genera…

NAST概述

一、NATS介绍 NATS是由CloudFoundry的架构师Derek开发的一个开源的、轻量级、高性能的&#xff0c;支持发布、订阅机制的分布式消息队列系统。它的核心基于EventMachine开发&#xff0c;代码量不多&#xff0c;可以下载下来慢慢研究。 不同于Java社区的kafka&#xff0c;nats…

C++小白入门

1.1编写程序四步走&#xff1a;创建项目创建文件编写代码运行程序解决方案资源管理器&#xff1a;在新创建的项目下右键“源文件”-添加-“新建项”-“C文件&#xff08;.cpp&#xff09;”&#xff0c;给文件取名#include <iostream> using namespace std;int main() {c…

JavaScript新手学习手册-基础代码(二)

与上篇博客相接 一&#xff1a;函数&#xff1a; 案例&#xff1a;通过函数实现绝对值的输出 方法一&#xff1a; function absoluate(x){if(x>0){return x;}else{ return -x;}} 在控制台调用函数 方法二&#xff1a; var demo1 function(x){if(x>0){return x;}els…

springboot项目如何配置启动端口

文章目录0 写在前面1 配置文件(.yaml)--推荐2 配置文件(.properties)3 IDEA配置--不推荐4 写在最后0 写在前面 项目启动需要一个独立的端口&#xff0c;所以在此记录一下。 根据配置文件的后缀书写格式略有不同。 1 配置文件(.yaml)–推荐 若是.yaml后缀的配置文件&#xff0…

SIP网络定压功放 SIP735T机架式广播终端

一、描述SIP735T是广州新悦网络设备有限公司的一款合并式定压功放&#xff0c;支持标准SIP协议&#xff0c;具有10/100M以太网接口&#xff0c;后面板上有2组AUX音源输入和6.35mm接口的麦克风输入&#xff0c;可以输入本地音源&#xff0c;播放来自网络与本地的音频。同时配置5…

Spark UI

Spark UIExecutorsEnvironmentStorageSQLExchangeSortAggregateJobsStagesStage DAGEvent TimelineTask MetricsSummary MetricsTasks展示 Spark UI &#xff0c;需要设置配置项并启动 History Server # SPARK_HOME表示Spark安装目录 ${SPAK_HOME}/sbin/start-history-server…

【女神节】简单使用C/C++和Python嵌套for循环生成一个小爱心

目录 前言实现分析代码实现代码如下效果如下优化效果代码如下效果如下总结尾叙前言 女神节马上到了,有女朋友的小伙伴是不是已经精心准好礼物了呢!对于已婚男士,是不是整愁今天又该送什么礼物呢!说真的,我也整愁着,有什么要推荐么,评论留言下! 实现分析 可以先在纸上或…

HashMap底层实现原理及面试题

文章目录1. 常见的数据结构有三种结构1.1 各自数据结构的特点2. HashMap2.1 概述2.2 底层结构2.2.1 HashMa实现原理&#xff1a;2.2.1.1 map.put(k,v)实现原理2.2.1.2 map.get(k)实现原理2.2.1.3 resize源码2.2.2 HashMap常用的变量2.2.3 HashMap构造函数2.3 JDK1.8之前存在的问…

JUC并发编程——多把锁

一、多八锁 多把不相干的锁 一间大屋子有两个功能&#xff1a;睡觉、学习&#xff0c;互不相干。 现在小南要学习&#xff0c;小女要睡觉&#xff0c;但如果只用一间屋子&#xff08;一个对象锁&#xff09;的话&#xff0c;那么并发度很低 解决方法就是准备多个房间&#x…

Validator校验之ValidatorUtils

注意&#xff1a;hibernate-validator 与 持久层框架 hibernate 没有什么关系&#xff0c;hibernate-validator 是 hibernate 组织下的一个开源项目 。 hibernate-validator 是 JSR 380&#xff08;Bean Validation 2.0&#xff09;、JSR 303&#xff08;Bean Validation 1.0&…

python实现半色调技术图像转换

半色调技术 半色调技术是一种将灰度图像转换为黑白图像的技术。它是通过将灰度图像的像素值映射到黑白像素值上来实现的。 比如说&#xff0c;在一块只能显示纯黑或纯白的屏幕上&#xff0c;如何将一张灰度图显示出灰度的效果&#xff0c;这时就可以用半色调技术实现。 如下…

SpringBoot整合MybatisPlus

文章目录前言一、MybatisPlus是什么&#xff1f;二、使用步骤1.导入依赖2.编写配置文件3.编写Controller和实体类4.编写持久层接口mapper5.启动类加包扫描注解6.测试总结前言 本篇记录一下SpringBoot整合MybatisPlus 一、MybatisPlus是什么&#xff1f; MyBatis-Plus&#xff…