剑指offer --- 从尾到头打印链表

news2024/11/27 4:40:31

目录

前言

一、读懂题目

二、思路分析

三、代码呈现

总结


前言

        当我们需要访问单向链表中特定位置值时,算法复杂度往往是O(n),在得到靠后节点的值时不可避免地从前向后遍历访问链表,那么当应题目要求从尾到头打印链表时,至少对每个链表节点需要访问两次。那么有哪些方法可以实现题目反向输出地要求呢?


一、读懂题目

题目: 输入一个链表的头结点,从尾到头反过来打印出每个结点的值

为了便于分析描述,我们不妨设定链表节点的定义:

typedef struct ListNode
{
	int val;
	ListNode* next;
}ListNode;

该题目无论是否使用额外外部空间都可以实现:

1)当我们不使用额外空间时,就必须对链表结构下手,由于单链表的遍历都是由前向后,所以可以在首次遍历链表时将每个节点间的指针指向关系调转,从而接着按照新链表从头到尾打印即可实现题目的倒序打印;

2)当然,一般遍历打印的功能或题目都是更偏向于不希望我们改变原始结构,这时就需要额外空间出面解决问题了。我们想到先遍历到的元素后打印,和栈的特征完全吻合,那么利用栈实现就具有得天独厚的条件。另外,同样可以使用数组,使用新创建的临时链表都可实现类似的存储功能便于后续依次取出作打印,进而解决题目要求。

现在理清可行实现思路后,我们进行思路总结和代码呈现:

二、思路分析

结合实际需求和功能限制,我们全文只讲述不改变原始结构即利用额外空间的方法:

1)利用栈实现

2)利用存值的数组实现(比存节点的数组省空间)

3)利用创建新链表实现

        我们需要认识到,这三点实现方法中,利用栈和新链表的优势在于无需预先知道链表长度(节点数目),而数组若设定为静态数组局限性很大,很容易造成访问越界或空间浪费,所以数组需要设定为动态数组,且是可以随时根据内部元素size占容量capacity的比值来动态拓展数组空间。换而言之,当我们同存储n个节点信息用于打印时,数组类型可以仅存每个节点需要打印的数据类型,无需存储整个结构体变量,当然其他两方法也可以通过存储结构体类型的指针来实现节省空间,但是对每个节点元素打印操作前多了一次解引用操作还是会对效率少有影响,甚至栈也可设定为打印类型并非结构体指针类型,这样可以吸纳数组存储的优点,同时避开创建临时新链表的多余解指针操作。

        所以接下来我们只讨论只存储打印类型的栈来实现逆序输出的功能,剩余两者都是比较好实现的,可以通过栈来对照模拟。

三、代码呈现

首先给出简单单链表这里需要用到的基础函数: 

// 链表基础功能
ListNode* init_List()
{
	ListNode* head = new ListNode();
	if (head == nullptr) { return nullptr; }

	head->val = 0;
	head->next = nullptr;
}

ListNode* appendNode(ListNode* head, int val)
{
	assert(head);
	ListNode* node = new ListNode();
	node->val = val;
	node->next = nullptr;

	ListNode* cur = head;
	while (cur->next != nullptr)
	{
		cur = cur->next;
	}
	cur->next = node;
	return node;
}

void printList(const ListNode* head)     // 注意这里直接设定为const ListNode*
{
	assert(head);

	const ListNode* cur = head->next;
	while (cur != nullptr)
	{
		printf("%-8d", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

接着我们构建一个简单链表:

ListNode* test_list()
{
	ListNode* head = init_List();   // 带头节点的单链表
	
	appendNode(head, 10);
	appendNode(head, 9);
	appendNode(head, 8);
	appendNode(head, 7);
	appendNode(head, 6);

	return head;
}

利用栈实现逆序打印:

void test1()
{
    // 打印正序对比
	ListNode* head = test_list();
	printf("正序打印:");
	printList(head);

    // 遍历并将节点用于打印的值填入栈内
	const ListNode* cur = head->next;
	stack<int> st;
	while (cur != nullptr)
	{
		st.push(cur->val);
		cur = cur->next;
	}

    // 对栈遍历打印
	printf("逆序打印:");
	while (!st.empty())
	{
		printf("%-8d", st.top());
		st.pop();
	}
	printf("\n");
}

运行结果:

可以看到结果很理想,同时我们没有在首次遍历链表时使用计数器统计节点个数。

我们想到,栈和递归实际上是一回事,那可不可以写出对应的利用递归实现的功能代码呢?

利用递归实现逆序打印:

// 利用递归实现
void traversal_back_printList(const ListNode* node)
{
	if (node == nullptr) { return; }
	static const ListNode* const index = node;    // 很重要  通过index标记链表头节点的地址,避免打印其初始化val值0
	traversal_back_printList(node->next);
	if (node != index)
	{
		printf("%-8d", node->val);
	}
	else
	{
		printf("\n");
	}
}

需要注意的是,语句 “static const ListNode* const index = node;” 中,static用于保证递归过程中每次进入函数都不会改变index存储的是头结点所在地址的值,避免后续执行递归函数最外层时打印头结点初始化的值;第一个const用于等价接收cosnt ListNode* node节点,第二个const在这里可省略。

运行结果:


总结

在单向链表中,要实现逆序打印链表的要求,可以采用以下几种方法:

  1. 利用栈:遍历链表,将节点的值依次压入栈中,然后再依次弹出栈顶元素,即可实现逆序打印链表的效果。这种方法不需要改变链表的结构,适用于不知道链表长度的情况。

  2. 利用递归:通过递归的方式,先递归访问链表的后续节点,再打印当前节点的值,可以实现逆序打印链表。需要在递归函数中设置一个标记来避免打印头节点的初始化值。

  3. 利用新链表(New List)或数组(Array):遍历链表,将节点的值存储到新链表或数组中,然后按照逆序从新链表或数组中取出并打印节点的值。这种方法需要额外的空间来存储节点的值,适用于不想改变链表结构的情况。

需要注意的是,以上三种方法都可以实现逆序打印链表的功能,选择使用哪种方法取决于具体的需求和限制。根据题目要求或实际情况选择最合适的方法来实现。

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

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

相关文章

数据结构第二课 -----线性表之顺序表

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

2022年06月 Python(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 如下所示的2行代码,最后print()函数打印出来的结果是?( ) c = [[赵大,21,男,北京],

【论文阅读】Equivariant Contrastive Learning for Sequential Recommendation

【论文阅读】Equivariant Contrastive Learning for Sequential Recommendation 文章目录 【论文阅读】Equivariant Contrastive Learning for Sequential Recommendation1. 来源2. 介绍3. 前置工作3.1 序列推荐的目标3.2 数据增强策略3.3 序列推荐的不变对比学习 4. 方法介绍4…

力扣每日一题94:二叉树的中序遍历

题目描述&#xff1a; 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]示例 3&#xff1a; 输入&#x…

速学数据结构 | 循环队列怎么写才最高效?只需要掌握这些技巧

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《Linux深造日志》《C干货基地》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言一、什么是循环队列&#xff1f;二、如何实现循环队列&#xff1f;2.1 循环队列的结构2.2 循环…

leetcode:268. 丢失的数字(异或运算)

一、题目 函数原型&#xff1a; int missingNumber(int* nums, int numsSize) 二、思路&#xff1a; 0 - n缺失一个数字&#xff0c;那么将数组中所有的数字按位异或&#xff0c;再按位异或0 - n的所有数字&#xff0c;由于 x ^ x 0&#xff0c;0 ^ x x&#xff0c;因此最终运…

Win10/Win11总是自动更新,如何关闭自动更新?

参考:https://www.zhihu.com/search?q%E5%85%B3%E9%97%ADwindows%E8%87%AA%E5%8A%A8%E6%9B%B4%E6%96%B0&search_sourceSuggestion&utm_contentsearch_suggestion&typecontent 手动关闭 1 Windows服务 1.1 WinR输入services.msc打开服务 1.2 找到Windows Update…

黑马程序员项目-黑马点评

黑马点评1 短信登录 基于Session实现登录流程 发送验证码&#xff1a; 用户在提交手机号后&#xff0c;会校验手机号是否合法&#xff0c;如果不合法&#xff0c;则要求用户重新输入手机号 如果手机号合法&#xff0c;后台此时生成对应的验证码&#xff0c;同时将验证码进行…

从MFC初始化过程看rc文件的行为,并剖析关联控件变量的实质

以MFC对话框程序为例: 当我们打开资源编辑器时就可以很容易的添加各种控件窗口,资源编辑器实际上操作的是rc文件;那么这些控件窗口是何时被MFC创建与管理的 没有关联控件变量前,在资源编辑器中依然可以容易拖动控件,并显示出来;这个控件窗口是如何被创建和管理的: 资源编…

【C语法学习】13 - fscanf()函数

文章目录 1 函数原型2 参数3 返回值4 比较5 示例5.1 示例15.2 示例2 1 函数原型 fscanf()&#xff1a;从指定流stream读取格式化输入&#xff0c;函数原型如下&#xff1a; int fscanf(FILE *stream, const char *format, ...)2 参数 fscanf()函数参数包括三部分&#xff1a…

Node.js 中解析 HTML 的方法介绍

在 Web 开发中&#xff0c;解析 HTML 是一个常见的任务&#xff0c;特别是当我们需要从网页中提取数据或操作 DOM 时。掌握 Node.js 中解析 HTML 的各种方式&#xff0c;可以大大提高我们提取和处理网页数据的效率。本文将介绍如何在 Node.js 中解析 HTML。 基本概念 HTML 解析…

【广州华锐互动】VR野外求生技能学习,让你感受真实的冒险之旅!

随着科技的迅速发展&#xff0c;虚拟现实(VR)技术为人们提供了一个全新的、身临其境的探险体验。通过将用户带入一个仿真的、沉浸式的虚拟环境&#xff0c;VR互动体验让人们在安全的氛围中感受到野外探险的乐趣。本文将从视觉呈现、沉浸式体验、交互性和应用范围四个方面&#…

MATLAB颜色索引表---持续更新中--各个平台都可使用

MATLAB颜色索引表—持续更新中–各个平台都可使用

探索ChatGPT在学术写作中的应用与心得

随着人工智能的迅猛发展&#xff0c;ChatGPT作为一种强大的自然语言处理模型&#xff0c;逐渐在学术界引起了广泛的关注。本文将探讨ChatGPT在学术写作中的应用&#xff0c;并分享使用ChatGPT进行学术写作时的一些经验和心得。 01 — ChatGPT在学术写作中的应用 1.文献综述和…

【ChatOCR】OCR+LLM定制化关键信息抽取(附开源大语言模型汇总整理)

目录 背景技术方案存在的问题及解决思路关键信息提取结果其他解决方案替换文心一言LangChain大型多模态模型&#xff08;Large Multimodal Model, LMM&#xff09; 开源大模型汇总LLaMA —— Meta 大语言模型Stanford Alpaca —— 指令调优的 LLaMA 模型Lit-LLaMA —— 基于 na…

基于单片机的语音存储与回放系统设计

博主主页&#xff1a;单片机辅导设计 博主简介&#xff1a;专注单片机技术领域和毕业设计项目。 主要内容&#xff1a;毕业设计、简历模板、学习资料、技术咨询。 文章目录 主要介绍一、控制系统设计1.1 系统方案设计1.2 系统工作原理 二、硬件电路设计总电路设计图 三、 软件设…

shufflenet v2 yolo

设计理念 1x1卷积进行平衡输入和输出的通道大小&#xff1b;组卷积要谨慎使用&#xff0c;注意分组数&#xff1b;避免网络的碎片化&#xff1b;减少元素级运算。 ShuffleNet v2中弃用了1x1的group convolution操作&#xff0c;而直接使用了input/output channels数目相同的…

win10系统nodejs的安装npm教程

1.在官网下载nodejs&#xff0c;https://nodejs.org/en 2&#xff0c;双击nodejs的安装包 3&#xff0c;点击 next 4&#xff0c;勾选I accpet the terms in…… 5&#xff0c;第4步点击next进入配置安装路径界面 6,点击next&#xff0c;选中Add to PATH &#xff0c;旁边…

高压检测设备

比如&#xff1a;高压数字表、高压差分探头、指针式高压表、电流探枪、高压探棒 这些设备都是用来测量高压的&#xff0c;有的测电压&#xff0c;有的测电流。 高压数字表&#xff1a; 单独使用&#xff0c;功能很简单&#xff0c;有2个正负极探爪&#xff0c;把2个探爪连接到…

Linux 安装 RocketMq

RocketMq是阿里出品&#xff08;基于MetaQ&#xff09;的开源中间件&#xff0c;已捐赠给Apache基金会并成为Apache的顶级项目。基于java语言实现&#xff0c;十万级数据吞吐量&#xff0c;ms级处理速度&#xff0c;分布式架构&#xff0c;功能强大&#xff0c;扩展性强。 官网…