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

目录

前言

一、读懂题目

二、思路分析

三、代码呈现

总结


前言

        当我们需要访问单向链表中特定位置值时,算法复杂度往往是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.mfbz.cn/a/115178.html

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

相关文章

matlab双目标定中基线物理长度获取

在MATLAB进行双目摄像机标定时,通常会获得相机的内参,其中包括像素单位的焦距(focal length)以及物理单位的基线长度(baseline)。对于应用中的深度估计和测量,基线长度的物理单位非常重要,因为它直接影响到深度信息的准确性。有时候,您可能只能获取像素单位的焦距和棋…

布雷斯悖论和借贷式拥塞控制

先看布雷斯悖论&#xff0c;新增一条路不但没减少交通延滞&#xff0c;反而降低了服务水准&#xff0c;下面一个简单的例子&#xff1a; 关于布雷斯悖论的讨论已经太多&#xff0c;我给出个新解释&#xff0c;这和我引出 借贷式拥塞控制 (差论证和编码)有关。 看一个不严谨但更…

Airtest工具根据App页面文字信息提取坐标进行截图保存在自定义文件夹

Airtest工具根据App页面文字信息提取坐标进行截图保存在自定义文件夹 一、项目背景 在一个项目中&#xff0c;选项被选中和未选中的节点元素的属性值无变化&#xff0c;通过AI识别率达不到百分百&#xff0c;想着通过计算图片的HSV值来判断选择能否被选中。&#xff08;HSV比…

聊聊我对AI Agents技术的一些看法

小伙伴们&#xff01;我来兑现承诺啦&#xff5e; ps&#xff1a;接下来期待什么内容&#xff0c;欢迎在评论区留言&#xff01; 今天&#xff0c;我们就来聊聊大模型 Agent。 最近这几个月&#xff0c;Agent 这一概念可谓火出天际&#xff0c;从 AutoGPT 一周 6 万 star 刷新…

云安全—K8s APi Server 6443 攻击面

0x00 前言 在未授权的一文中&#xff0c;详细描述了k8s api中的8080端口未授权的问题&#xff0c;那么本篇主要来说6443端口的利用。 0x01 API连接攻击面 1.匿名用户访问 匿名开放方式&#xff1a;kubectl create clusterrolebinding cluster-system-anonymous --clusterro…

K8S部署时IP问题

本次环境搭建需要安装三台Centos服务器&#xff08;一主二从&#xff09;&#xff1b;搭配的前提时做好ip的设置 主机IP规划 IP地址的设定需要根据自己主机来设置&#xff0c;在虚拟机的虚拟网络编辑器中看他给你的ip&#xff1b;不要查什么ipconfig了。 在虚拟网络编辑器中…

Ansible中的任务执行控制

循环 简单循环 {{item}} 迭代变量名称 loop: - value1 - value2 - ... //赋值列表{{item}} //迭代变量名称循环散列或字典列表 - name: create filehosts: host1tasks:- name: file moudleservice:name: "{{ item.name }}"state: "{{…

UG\NX二次开发 先设置默认颜色再创建对象

文章作者:里海 来源网站:里海NX二次开发3000例专栏 感谢粉丝订阅 感谢 qq_42461973 订阅本专栏,非常感谢。 简介 有粉丝问,可不可以先设置默认颜色再创建对象?这个是可以的,下面是例子: 效果 代码 #include "me.hpp" using namespace std;

毅速丨3D打印结合拓扑优化让轻量化制造更容易

轻量化可以减少产品的重量&#xff0c;提高产品的性能和效率&#xff0c;同时减少能源消耗和排放。尤其在航空航天、汽车制造造等行业对轻量化追求更高。当前&#xff0c;随着制造技术的发展&#xff0c;拓扑优化结合3D打印为轻量化制造带来的显著的优势正在逐渐凸显。 首先&am…

APM建设踩了哪些坑?去哪儿旅行分布式链路追踪系统实践

一分钟精华速览 分布式链路追踪系统在企业的APM体系中扮演着重要的角色。本文分享了去哪儿旅行构建分布式链路追踪系统的实践经验。从APM整体架构设计入手&#xff0c;讲述了日志收集、Kafka传输和Flink任务处理等环节的性能优化实践和踩坑经验。 同时&#xff0c;作者结合丰…

绝地求生msvcp140.dll丢失报错怎么办,这四个方法都可以解决

在回答这个问题之前&#xff0c;我们先来了解一下什么是msvcp140.dll。msvcp140.dll是微软Visual C 2015 Redistributable的一个组件&#xff0c;它包含了许多运行库文件&#xff0c;用于支持各种应用程序的正常运行。当你在玩《绝地求生》&#xff08;俗称“吃鸡”&#xff09…

深入了解 CPU 的型号、代际架构与微架构

大家好&#xff0c;我是飞哥&#xff01; 在 10 月 16 号的时候&#xff0c;Intel 正式发布了第 14 代的酷睿处理器。但还有很多同学看不懂这种发布会上发布的各种 CPU 参数。借着这个时机&#xff0c;我给大家深入地讲讲 CPU 的型号规则、代际架构与微架构方面的知识。 CPU 在…

谈一谈SQLite、MySQL、PostgreSQL三大数据库

每一份付出&#xff0c;必将有一份收货&#xff0c;就像这个小小的果实&#xff0c;时间到了&#xff0c;也就会开花结果… 三大数据库概述 SQLite、MySQL 和 PostgreSQL 都是流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;但它们在功能、适用场景和性…

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

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

【第28例】IPD体系进阶 | 需求管理:需求实现过程

目录 简介 内容详解 CSDN学院相关推荐 作者简介 简介 继续 IPD 体系中的需求管理相关的专题。 先来看看整个需求管理涉及的过程内容: 需求管理流程主要包含五个阶段: 需求收集; 需求分析; 需求分发/分配;

EasyExcel动态复杂表头导出方法

目录 需求分析解决方案数据问题数据导入 需求分析 公司数据比较特殊有一部分数据需要动态修改导致信息导入时表头是不确定的&#xff0c;但其中又有一部分表头是固定的&#xff0c;如下图所示&#xff0c;如果表头全部是固定的话可以通过EasyExcel实体类的注解很轻松的解决&am…

Java自学第3课:Java语言流程控制和字符串

1 复合语句 复合语句是以区块为单位的语句&#xff0c;也就是{}内的内容。 2 条件语句 if (布尔表达式){语句序列}else{语句序列} 有个好玩的是&#xff0c;对年龄段的分段&#xff0c;其实以前的思维是有点冗余的&#xff0c;比如a<100 & a>90&#xff0c;在复合…

文本内容转换成语音播放的工具:Speech Mac

Speech Mac版是一款适用于Mac电脑的语音合成工具。它将macOS语音合成器的所有功能整合到一个易于使用的界面中。通过Speech Mac版&#xff0c;用户可以选择40多种声音和语言&#xff0c;方便地将文本转换为语音。用户可以将文本拖放或粘贴到Speech中&#xff0c;并随时更改语音…

巴黎奥运会将基于阿里云实现云上转播

10月31日&#xff0c;2023杭州云栖大会&#xff0c;奥林匹克广播服务公司与奥林匹克频道服务公司首席技术官索蒂里斯萨拉穆里斯&#xff08;Sotiris SALAMOURIS&#xff09;表示&#xff0c;过去5年阿里云作为奥运会转播的基础设施&#xff0c;让奥运故事触达了更多全球观众。 …

mybatis-plus技巧--动态表名-多语句-拼接sql--关于mybatis的mysql分页查询总数的优化思考

文章目录 动态表名xml表名填充表名拦截器每天按统计每次设置 多语句操作forEach动态拼接 参数构建java进行拼接sqlmysql分页查询总数count不要使用count&#xff08;常数&#xff09;&#xff0c;count&#xff08;列名&#xff09;代替count(*)自己计数 SQL_CALC_FOUND_ROWSxm…