【数据结构】单链表经典算法题的巧妙解题思路

这篇具有很好参考价值的文章主要介绍了【数据结构】单链表经典算法题的巧妙解题思路。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

题目

1.移除链表元素

2.反转链表

3.链表的中间节点

4.合并两个有序链表

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

解析

题目1:创建新链表

题目2:巧用三个指针

题目3:快慢指针

题目4:哨兵位节点

题目5:环形链表


 文章来源地址https://www.toymoban.com/news/detail-860739.html

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

介绍完了单链表,我们这次来说几个经典的题目,本篇的题目衔接下一章双向链表哦~ 

题目

1.移除链表元素

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

 

2.反转链表

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

 

3.链表的中间节点

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

 

4.合并两个有序链表

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

 

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

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

我们来看看本篇要介绍的解法吧 

解析

这里介绍的解析仅供参考,算法题的实现思路是很多的,小编就不一一详细介绍了,我们主要介绍小编认为比较好的方法~

题目1:创建新链表

思路:找值不为val的节点,尾插到新链表中

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 pcur不等于val时,就把节点放在新链表中,新链表初始为空链表,所以新链表的第一个节点既是newhead也是newtail,放好后pcur往后走,继续判断节点是否等于val

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 pcur所指向的节点不等于val,把这个节点尾插到新链表里面,newhead不变,newtail往后走,pcur也往后走,继续判断

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 此时pcur指向的节点值为val,pcur再往后走,其余不变

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

一直到pcur遍历完原链表,此时新链表就是去掉所有等于val值的节点的链表

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 我们代码实现一下

首先把结构体类型 struct ListNode 换个名字,方便定义,就换成ListNode

typedef struct ListNode ListNode;

 然后在题目给的函数中进行实现

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) 
{
	ListNode* newhead, * newtail;//定义新头节点、新尾节点
	newhead = newtail = NULL;//新链表初始时为空
	ListNode* pcur = head;//pcur初始时指向原链表首节点
	while ()//开始遍历链表
	{

	}
}

循环结束的条件应该是pcur不为NULL,循环体里面就是刚刚分析的过程

struct ListNode* removeElements(struct ListNode* head, int val) 
{
	ListNode* newhead, * newtail;//定义新头节点、新尾节点
	newhead = newtail = NULL;//新链表初始时为空
	ListNode* pcur = head;//pcur初始时指向原链表首节点
	while (pcur)//开始遍历链表
	{
		if (pcur->val != val)//找pcur不为val的节点
		{
			if (newhead == NULL)//新链表为空
			{
				newhead = newtail = pcur;
			}
			else//新链表不为空
			{
				newtail->next = pcur;
				newtail = newtail->next;
			}
		}
		pcur = pcur->next;//继续往后遍历
	}
	if(newtail)
	    newtail->next = NULL;
    return newhead;
}

 

 

题目2:巧用三个指针

参考思路1:依旧是新创建一个链表,遍历原链表,将原链表中的节点采用头插的形式放在新链表中,就可以实现链表的翻转

参考思路2:创建3个指针,完成原链表的翻转

我们来实现思路2,这个思路一般不太好想

创建3个变量,n1、n2、n3初始时分别指向NULL、节点1、节点2

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

然后让n2指向n1,n1走到n2的位置,n2走到n3的位置,n3走向下一个节点

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

然后再重复上面的步骤,n2指向n1,n1走到n2的位置,n2走到n3的位置,n3走向下一个节点,

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 走到这里的时候n3就不能继续走了,n1和n2继续走

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

此时n1就是链表的新头节点

我们来代码实现,依旧是重命名一下,把结构体类型 struct ListNode 换个名字,方便定义,换成ListNode

typedef struct ListNode ListNode;

然后在题目给的函数中实现

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) 
{
	ListNode* n1, * n2, * n3;//创建3个变量
	n1 = NULL;//给三个变量赋值
	n2 = head;
	n3 = n2->next;
	while (n2)
	{
		n2->next = n1;
		n1 = n2;
		n2 = n3;
		if(n3) //n3为不空时才继续往后走
			n3 = n3->next;;
	}
	return n1;
}

这是链表不为空的情况,那链表为空时呢?直接返回 head 就可以了

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) 
{
	if (head == NULL) //空链表
		return head;
	//非空链表
	ListNode* n1, * n2, * n3;//创建3个变量
	n1 = NULL;//给三个变量赋值
	n2 = head;
	n3 = n2->next;
	while (n2)
	{
		n2->next = n1;
		n1 = n2;
		n2 = n3;
		if(n3) //n3为不空时才继续往后走
			n3 = n3->next;;
	}
	return n1;
}

 

 

题目3:快慢指针

这道题的思路也很多,本篇想通过这题介绍一种很妙的方法,快慢指针,快慢指针在之后的很多题中也有很多应用

先来介绍一下快慢指针,slow表示慢指针,fast表示快指针

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

慢指针slow一次往后走一步,快指针fast一次往后走两步

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

fast没有走到空,继续走,slow一次往后走一步,fast一次往后走两步

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

fast不能再继续往后走,此时slow所指向的节点就是链表的中间节点,这是节点个数为奇数的情况如果节点个数为偶数个呢?我们来看看

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

依旧是慢指针slow一次往后走一步,快指针fast一次往后走两步

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

此时fast走到空了,不再继续往后走,slow所指向的节点正是第二个中间节点

这就很简单了,我们将快慢指针运用到题目中,代码实现一下

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

这里需要注意:while循环里面的判断条件 (fast && fast->next) 中 fast 不能和 fast->next 交换位置,因为如果链表结点个数是偶数个时,fast会走到NULL,当fast走到NULL时,while再对fast->next进行判断,此时可以对空指针解引用吗?不可以。所以fast->next不可以放在前面。当fast放在前面,此时fast为NULL,直接退出循环,根本就不会对后面的表达式进行判断,也就不会出现对空指针解引用的情况。

 

 

题目4:哨兵位节点

我们先说没有哨兵位的代码,哨兵位后面再分析

这题我们先选择创建新链表,遍历原链表,我们定义两个指针遍历两个链表

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

值小的节点放在新链表中,相等的话随便放哪个都行,然后让相应的指针往后走一个【数据结构】单链表经典算法题的巧妙解题思路,数据结构

此时L1和L2比较,L1比L2小,把L1的节点放到新节点,然后L1往后移

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

然后再依次向后比较,一直到L1或L2走到NULL为止,遍历结果只有两种情况,要么L1为空,要么L2为空

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 我们代码实现一下,先写L1的值小于L2的值的时候

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
	ListNode* L1 = list1; //遍历原链表
	ListNode* L2 = list2;
	ListNode* newhead, * newtail; //创建新链表
	newhead = newtail = NULL;
	while (L1 && L2)//循环遍历,有一个走到NULL就退出
	{
		if (L1->val < L2->val)//L1的节点值小
		{
			if (newhead == NULL) //新链表为空链表
			{
				newhead = newtail = L1;
			}
			else //新链表不为空链表
			{
				newtail->next = L1;//把L1放在新链表
				newtail = newtail->next;
			}
			L1 = L1->next;//L1往后走
		}
		else //L2的节点值小
		{

		}
	}
}

L2小于L1的时候也是类似的,此时while循环内代码为

	while (L1 && L2)//循环遍历,有一个走到NULL就退出
	{
		if (L1->val < L2->val)//L1的节点值小
		{
			if (newhead == NULL) //新链表为空链表
			{
				newhead = newtail = L1;
			}
			else //新链表不为空链表
			{
				newtail->next = L1; //把L1放在新链表
				newtail = newtail->next;
			}
			L1 = L1->next;//L1往后走
		}
		else //L2的节点值小
		{
			if (newhead == NULL) //新链表为空链表
			{
				newhead = newtail = L2;
			}
			else //新链表不为空链表
			{
				newtail->next = L2;//把L2放在新链表
				newtail = newtail->next;
			}
			L2 = L2->next;//L2往后走
		}
	}

跳出循环后有两种情况,要么L1先走到空,要么L2先走到空 ,上面的情况就是L1走到空,L2没有走到空,我们直接把L2拿下来尾插就行了,出while循环后的代码如下

    //跳出循环后
    if (L2) //L2没走到空
	    newtail->next = L2;
    if (L1) //L1没走到空
	    newtail->next = L1;
return newhead;

我们还需要处理一下空链表的情况,空链表的情况代码放在函数体最前面

	//空链表的情况
	if (list1 == NULL)
		return list2;
	if (list2 == NULL)
		return list1;

 

我们会发现代码重复的部分很多,我们如何去优化它?首先要找为什么会重复

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

我们向操作系统申请一个空间,这个空间不存有效数据

newhead = newtail = (ListNode*)malloc(sizeof(ListNode));

此时链表也改变了

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 所以现在的代码就变了,返回值也变了

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
	//空链表的情况
	if (list1 == NULL)
		return list2;
	if (list2 == NULL)
		return list1;
	ListNode* L1 = list1; //遍历原链表
	ListNode* L2 = list2;
	ListNode* newhead, * newtail; //创建新链表
	newhead = newtail = (ListNode*)malloc(sizeof(ListNode));

	while (L1 && L2)//循环遍历,有一个走到NULL就退出
	{
		if (L1->val < L2->val)//L1的节点值小
        {
	        newtail->next = L1; //把L1放在新链表
	        newtail = newtail->next;
	        L1 = L1->next;//L1往后走
        }
        else //L2的节点值小
        {
	        newtail->next = L2;//把L2放在新链表
	        newtail = newtail->next;
	        L2 = L2->next;//L2往后走
        }
	
	}
	//跳出循环后
	if (L2) //L2没走到空
		newtail->next = L2;
	if (L1) //L1没走到空
		newtail->next = L1;
	return newhead->next;
}

我们malloc的空间要记得释放,所以可以在return上面加三排代码,而且return的值也要改一下

    ListNode* ret = newhead->next;//存newhead->next的值
    free(newhead);
    newhead = NULL;
    return ret;

所以这里的头节点就是哨兵位

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

题目5:环形链表

我们先介绍一下约瑟夫问题的历史背景

据说著名犹太 历史学家 Josephus( 弗拉维奥·约瑟夫斯 )有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。约瑟夫将自己和他的朋友安排在16和31个位置,于是逃脱了这场死亡游戏

来看一下环形链表

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

我们画个图简单介绍一下

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

此时把3节点去掉,继续从下一个存在的节点开始报数

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

再把节点1去掉,继续从下一个存在的节点开始报数

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

把节点5去掉,现在留下两个节点,如果约瑟夫和他的朋友想要存活,就要站在2或4的位置

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

那么这道题就是类似,不过是留下一个节点

5个节点,报数为2举例

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

最后就留下了节点3

代码中我们如何分析呢?看下图。

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

数1之后数2,此时pcur后移,prev后移

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

pcur所指向的节点数到了2,要销毁pcur所指的节点,销毁的时候先让prev的next指针指向pcur的next

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

然后把pcur指向的节点销毁,并且让pcur指向下一个节点,也就是prev的next指针指向的位置

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

然后继续数数,从1开始

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

然后就是重复步骤,当只剩下两个节点时,分析一下

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

pcur报数为2,删掉5这个节点,要先让prev的next指针指向pcur的next,也就是节点3此时自己指向自己

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

然后再销毁节点5,此时pcur也指向节点3 

【数据结构】单链表经典算法题的巧妙解题思路,数据结构大概分析就是这样,我们代码实现一下

先写一个创建节点的函数

 typedef struct ListNode ListNode;
 ListNode* BuyNode(int x)//创建节点
 {
    ListNode* node=(ListNode*)malloc(sizeof(ListNode));
    if(node==NULL)
    {
        exit(1);
    }
    node->val = x;
    node->next = NULL;
    return node;
 }

然后写带环链表的函数

 ListNode* CreatCircle(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;
 }

然后在ysf函数中实现

int ysf(int n, int m )
{
   ListNode* prev = CreatCircle(n);//环形链表尾节点由prev接收
   ListNode* pcur = prev->next;//头节点由pcur接收
   int count = 1;
   while (pcur->next != pcur)
   {
      if(count == m) //需要销毁节点
      {
        prev->next = pcur->next;
        free(pcur);
        pcur = prev->next;
        count = 1;//重新计数
      }
      else //不用销毁节点
      {
        prev = pcur;
        pcur = pcur->next;
        count++;
      }
   }
    return pcur->val;
}

 

 这次分享就到这里了,拜拜~

【数据结构】单链表经典算法题的巧妙解题思路,数据结构

 

 

 

到了这里,关于【数据结构】单链表经典算法题的巧妙解题思路的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【数据结构与算法】单链表的排序算法(选择,冒泡,递归)

    【数据结构与算法】单链表的排序算法(选择,冒泡,递归)

    目录 选择排序 冒泡排序 快速排序 合并两条链表并排序 选择排序 链表的选择排序思想与数组的排序类似,但是链表需要先找到里面最小或者最大的值,然后将这个值用改链语句进行操作 我们先看这个改链语句的操作(min是笔者打错了应该是max,但是图已经画好了就没有改)

    2024年02月04日
    浏览(13)
  • 【数据结构与算法】单链表的插入和删除

    🔥 本文由 程序喵正在路上 原创,CSDN首发! 💖 系列专栏: 数据结构与算法 🌠 首发时间:2022年9月21日 🦋 欢迎关注🖱点赞👍收藏🌟留言🐾 🌟 一以贯之的努力 不得懈怠的人生 众所周知,顺序表中的每个结点中只存放数据元素,其优缺点为: 优点:可随机存取,存储

    2024年02月07日
    浏览(13)
  • 【算法基础】数据结构| 单链表+双链表 代码实现+图解+原理

    【算法基础】数据结构| 单链表+双链表 代码实现+图解+原理

    博主简介: 努力学习的预备程序媛一枚~ 博主主页: @是瑶瑶子啦 所属专栏: Java岛冒险记【从小白到大佬之路】 因为瑶瑶子正在备战蓝桥杯和校内ACM选拔赛,最近在学习算法相关的知识。我是借助 AcWing网站 来学习的,这篇文章是我学习就我学习内容的一个笔记,其中的一些

    2024年02月01日
    浏览(52)
  • 【算法与数据结构】 C语言实现单链表队列详解

    【算法与数据结构】 C语言实现单链表队列详解

    前面我们学习了队列的顺序表的实现,本节将用单链表实现队列。 队列也可以数组和链表的结构实现, 使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低 。下面我们先复习一下队列的基本概念: 队列:只允许在一端进行插入

    2024年04月11日
    浏览(39)
  • 【数据结构与算法】单链表的增删查改(附源码)

    【数据结构与算法】单链表的增删查改(附源码)

      这么可爱的猫猫不值得点个赞吗 😽😻 目录 一.链表的概念和结构 二.单链表的逻辑结构和物理结构 1.逻辑结构  2.物理结构 三.结构体的定义 四.增加 1.尾插   SListpushback 2.头插  SListpushfront 五.删除 1.尾删  SListpopback 2.头删  SListpopfront 六.查找  插入  释放   打印 1.查找

    2024年02月02日
    浏览(39)
  • 单链表的建立(头插法、尾插法)(数据结构与算法)

    单链表的建立(头插法、尾插法)(数据结构与算法)

    如果要把很多个数据元素存到一个单链表中,如何操作? 1.初始化一个单链表 2. 每次取一个数据元素,插入到表尾/表头 尾插法建立的单链表元素顺序与输入数据集合的顺序相同,即按照输入数据的顺序排列。 使用尾插法建立单链表的一个常见应用是在计算机科学中进行数据

    2024年04月11日
    浏览(12)
  • 【数据结构与算法】深入浅出:单链表的实现和应用

    【数据结构与算法】深入浅出:单链表的实现和应用

      🌱博客主页:青竹雾色间. 😘博客制作不易欢迎各位👍点赞+⭐收藏+➕关注  ✨ 人生如寄,多忧何为  ✨ 目录 前言 单链表的基本概念 节点 头节点 尾节点 单链表的基本操作 创建单链表 头插法: 尾插法: 插入(增)操作  删除(删)操作: 查找(查)操作: 修改(改

    2024年02月08日
    浏览(14)
  • C语言简单的数据结构:单链表的有关算法题(2)

    C语言简单的数据结构:单链表的有关算法题(2)

    接着我们介绍后面的三道题,虽然代码变多了但我们的思路更加通顺了 题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/ 创建新链表,遍历原链表,将节点值小的进行尾插到新链表中 这里要多次进行对NULL的判断,开始传入列表,中间newHead的判断,循环出来一个为NULL的判断

    2024年04月15日
    浏览(41)
  • 【一起学数据结构与算法】快速教你了解并实现单链表

    【一起学数据结构与算法】快速教你了解并实现单链表

    此篇是对单链表知识的学习和实现,基本上大体的方法实现和思路都已经表达,如果有不对的地方,还请各位大佬多多指教! 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素

    2024年02月19日
    浏览(7)
  • 数据结构(五)数据结构与算法中的经典题

    本文是在原本数据结构与算法闯关的基础上总结得来,加入了自己的理解和部分习题讲解。至此数据结构介绍已完结,后续会把数据结构算法题系列更完。 原活动链接 邀请码: JL57F5 根据要求完成题目 Q1. (单选)以下哪些数据结构支持随机访问? A. 数组 B. 单链表 C. 双向链表

    2024年01月20日
    浏览(11)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包