《算法之美》の链表问题の获得链表中倒数第m个元素

习题3.5 求链表的倒数第m个元素 请设计时间和空间上都尽可能高效的算法,在不改变链表的前提下,求链式存储的线性表的倒数第m(>0)个元素。 函数接口定义: ElementType Find( List L, int m ); 其中List结构定义如下: typedef struct Node *PtrToNode; struct Node { ElementType Data; /* 存储结点数据 */ PtrToNode Next; /* 指向下一个结点的指针 */ }; typedef PtrTo 阅读详情

问题:

给定一个单向链表,设计一个时间优化并且空间优化的算法,找出该链表的倒数第m个元素。实现您的算法,注意处理相关的出错情况。m定义为当m=0时,返回链表最后一个元素。

 

解答:

这是一个难题,因为单向链表只能正向遍历,这个问题需要根据元素与链表尾的相对位置来找出该元素,但是当发现链表尾时,没有简单的办法回溯到倒数第m个元素。

 

我们需要的是倒数第m个元素,所以,如果我们从某个元素开始,遍历了m个元素之后刚好到达链表末尾,那这个元素就是要找的元素。一种方法是简单的以这种方式检查每个元素,直到找到要找的元素为止。但这样同样的元素会被遍历多次,针对链表中大部分元素我们都会遍历m个元素,如果链表的长度是n的话,那么这个算法的时间复杂度就是O(mn)。

 

也许从链表的尾部倒推回去不是最好的办法,那么我们可以从链表头开始计数。

思路一:想要的元素是倒数第m个元素,而我们知道m值。它肯定是从链表头算起的第x个元素,且x+m=n,即链表的长度。而计算出链表的所有元素个数是容易的。之后就可以算出x=n-m,并从链表头开始遍历x个元素。尽管这个算法需要2次遍历链表,但它的时间复杂度仍是O(n)。同时,如果我们可以修改链表的函数,在添加元素时对一个计数遍历加1,在删除元素时对这个计数变量减1,我们就可以省掉链表长度计算的过程,使它更有效率。

上面思路的缺点是:如果这是一个很长的链表而计算机的内存又有限,那么这个链表的大部分很可能都存放在切换出物理内存的虚拟内存上(即存放在磁盘上)。如果真是这样,那么链表的每次遍历都将需要做大量的磁盘读写操作才能把链表的有关部分读到物理内存里来进行处理。在这种情况下,如果算法只需对链表做一次完整的遍历,那么它将比必须做两次遍历的算法快很多---虽然它们都是O(n)级的算法。

 

但是,如果不能修改链表的数据结构呢?在上面的分析中,当我们到达链表的末尾时,实际上只对保存下来的m个元素中的一个感兴趣,即当前位置的前面第m个元素。我们之所以记下m个元素只是因为当前位置的前面第m个元素在每次移动位置时都会改变。保持一个m个元素的队列,每次在移动当前位置时,将当前元素添加在头部,在尾部删除一个元素,这样做只是为了确保队列中最后一个元素总是当前位置的前面第m个元素。

 

思路二:透过想象看本质,上面我们使用这个m个元素的数据结构来隐式地移动一个前m个元素的指针,保持它与当前位置的指针同步移动。但是,这个数据结构是不必要的,我们可以显式地移动前面第m个元素的指针,就像移动当前位置的指针一样,这与通过一个队列隐式地移动同样简单,这样一来,就无需保存当前位置和前面第m个元素之间的所有元素。这个算法的优点是:线性时间复杂度、一次遍历、可以忽略的存储要求。

 

此时,我们需要使用两个指针:一个当前位置指针和一个前面第m个元素的指针。需要确保两个指针之间相差m个元素,然后以同样的速度移动它们。如果当前指针到达链表的末尾,前面第m个元素的指针就是指向倒数第m个元素。同时要注意边界条件,如果链表的长度小于m,那么就没有倒数第m个元素,因此,在移动当前指针时要检查是否到达链表的末尾。

 

实现代码如下:

#include <iostream>

#include <assert.h>

 

struct ListNode

{

    int m_nKey;

    ListNode* m_pNext;      

};

 

void InitList(ListNode** pList)

{

    *pList = (ListNode*)malloc(sizeof(ListNode));

    (*pList)->m_pNext = NULL;

}

 

void InsertList(ListNode* pList, int data)

{

     assert(pList != NULL);

     ListNode* pNewNode = (ListNode*)malloc(sizeof(ListNode));

     pNewNode->m_nKey = data;

     pNewNode->m_pNext = pList->m_pNext;

     pList->m_pNext = pNewNode;

}

 

//思路一的核心算法

//注意:链表最后一个元素是倒数第0个

ListNode* findMLastElement1(ListNode* head, unsigned int m)

{

    if(head == NULL)

        return NULL;       

   

    //计算链表中节点数º

    ListNode* pCurrent = head->m_pNext;

    unsigned int nCount = 0;

    while(pCurrent != NULL)

    {

        pCurrent = pCurrent->m_pNext;

        nCount++;                       

    }

   

    //如果链表中节点数少于m,返回NULL

    if(nCount < m)

        return NULL;

       

    //链表倒数第m个节点就是从链表开头第nCount-m个节点

    //注意:m从0开始

    pCurrent = head;

    for(unsigned int i=0; i<nCount-m; i++)

    {

        pCurrent = pCurrent->m_pNext;            

    }

    return pCurrent;

}

 

//思路二的核心算法

//注意:链表最后一个元素是倒数第个

ListNode* findMLastElement2(ListNode* head, unsigned int m)

{

    if(head == NULL)

        return NULL;

   

    ListNode *current, *mBehind;

    current = head;

    for(int i=0; i<m; i++)

    {

        //current设置为当前指针,它前面必须存在第m个元素,否则出错

        if(current->m_pNext != NULL)

        {

            current = current->m_pNext;                   

        }       

        else

        {

            return NULL;   

        }

    }         

   

    mBehind = head;  //当前位置current的前面第m个元素的指针,从链表头开始移动

    while(current->m_pNext != NULL)

    {

        current = current->m_pNext;

        mBehind = mBehind->m_pNext;                      

    }

    return mBehind;

}

 

//打印链表元素

void PrintListNormally(ListNode* pListHead)

{

    ListNode* pTempNode = pListHead->m_pNext;

    while(pTempNode != NULL)

    {

        std::cout<<pTempNode->m_nKey<<std::endl;

        pTempNode = pTempNode->m_pNext;            

    }    

}

 

int main()

{

    ListNode* pListHead = NULL;

    InitList(&pListHead);

    for(int i=9; i>=0; i--)

    {

        InsertList(pListHead, i);           

    }

   

    PrintListNormally(pListHead);

   

    ListNode* findNode = NULL;

   

    findNode = findMLastElement1(pListHead, 3);

    std::cout<<"链表中倒数第3个元素是:"<<findNode->m_nKey<<std::endl;

   

    findNode = findMLastElement2(pListHead, 4);

    std::cout<<"链表中倒数第4个元素是:"<<findNode->m_nKey<<std::endl;

   

    system("pause");

    return 0;

}

 

扩展问题:

如何获得处于单链中间位置的节点(只能遍历链表一次!)

解答:

其实原理和上面的一样,就是设置两个指针,或游标(形象点):

Element *pSlow = head;      //标识当前节点

Element *pFast = head;        //标识当前节点的下一个节点

将慢游标前进步长设为1,快游标前进步长设为2:

pSlow = pSlow->next;         //前进一个节点

pFast = pFast->next->next;        //前进两个节点因此,快游标是慢游标速度的两倍,

//当快游标到达链表尾节点或尾节点前一个节点时,慢游标正好处于链表的

//中间位置,即为所求。

 

 

 

保姆级教程:在S32DS 3.4上安装S32K3xx RTD 4.4驱动包(附常见安装失败排查) 本文提供在S32DS 3.4上安装S32K3xx RTD 4.4驱动包的详细教程,涵盖环境准备、分步安装指南及常见问题排查。特别针对NXP S32K3系列MCU开发者,解决网络问题、路径配置等安装难点,并分享进阶配置技巧和最佳实践,助力高效开发。 阅读详情

相关推荐

基于DSP28335的Matlab/Simulink代码生成实验七:A/D采样

实验七:讲解如何使用DSP的ADC模块,通过代码生成技术控制DSP进行电流采样,是后续实现电机闭环控制的重要环节。通过简单的实验来熟悉代码生成的过程。最终实现复杂算法代码自动生成,进行高性能电机控制!

m0_73869369的博客 4724

[C++面试题]之单链表

接下来我会介绍C++面试题中第二个重要的板块——数据结构,主要包括字符串的使用、堆、栈、排序方法等,今天我们先来看单链表。 1、编程实现一个单链表的建立/测长/打印。 答案: #include<iostream> using namespace std; //单链表结构体 typedef struct student { int...

weixin_30781631的博客 266

泛微E9开发 明细表字段赋值到主表字段,将各类型合计赋值到主表类型合计中

①绑定明细表类型、预计金额字段的值改变事件,每当有值发生改变,就需要重新计算供应商1、供应商2、供应商3的合计,如果不绑定这两个字段的话,会出现计算不及时或计算错误的问题。②当有明细行被删除时,也需要重新计算供应商1、供应商2、供应商3的合计,比如删除了供应商1的明细行,如果不重新计算,那么供应商1的值还是被删之前的值,合计错误。将各类型合计赋值到主表类型合计中。

a_waste_only的博客 3836

求链表的倒数第m个元素

法一: 首先遍历一遍单链表,求出整个单链表的长度n,然后将倒数第m个,转换为正数第n-m+1个,接下去遍历一次就可以得到结果。 不过这种方法需要对链表进行两次遍历,第一次遍历用于求解单链表的长度,第二次遍历用于查找正数第n-m+1个元素。 1 ElementType Find( List L, int m ) 2 { 3 PtrToNode p; ...

weixin_33813128的博客 2413

HFSS微带线仿真从零到一:材料参数与求解设置优化指南

在射频电路设计中,微带线仿真涉及电磁场求解与传输线理论,其精度高度依赖材料参数和求解设置。相对介电常数、损耗角正切和导体电导率等参数直接影响特性阻抗与插入损耗,而求解频率、网格划分和参数扫描则决定仿真效率与收敛性。合理设置这些参数可显著提升S参数准确性,避免设计迭代。应用场景包括天线馈线、滤波器和微波互连。本文基于HFSS微带线仿真实践,系统讲解材料选择与参数优化全链路,帮助工程师快速复现可靠模型。

weixin_29192211的博客 79

ur3手眼标定+realsenseL515

知乎 csdn

AI_future的博客 978

问题 D: 案例3-1.3:求链表的倒数第m个元素(附加代码模式)

本题是附加代码模式,主函数main的代码会自动附加在同学们提交的代码后面,请同学们在提交的时候注释掉自己的main函数。给你一个链表,链表长度为n,链表内各元素为a1,a2....an,给你一个数m,求链表倒数第m个数是多少。第二行:n个数字,为链表元素从头到尾依次排列。第一行:链表长度n(1

weixin_65908362的博客 759

Power BI财销一体分析——KPI分析应用实践

第九步:插入三个散点图,行拖入分销商表分销商字段,将度量值中的销量拖入X轴,将利润拖入Y轴,大小拖入度量值总销售额,将分销商表业务类型字段作为筛选条件添加给散点图,分别选择专业自行车店、增值经销商、仓库,使视觉对象固定显示不同业务类型的散点分布。这个公式其实是不需要我们自己写的,依赖于快度量值功能星级评分,基值选择度量值总销售额,星数选择5,最低星值评分选择0,最高星级评分值选择100,点击添加,星级评分度量值就建立好了,接下来我们理解一下公式。好的,今天的讲解就到这里。

BI瓶子的博客 1552

第03次作业-栈和队列

1.学习总结 使用思维导图将数据结构绪论、线性表、栈和队列等知识点组织联系起来。 2.PTA实验作业 2.1题目1 2.2.1思路 1.首先就是把串全部读进来,合成一个大串。(把/**/ 改成<>代替)2.然后再对新生成的串处理,读入左括号,直接压入栈顶。如果读入的是右括号:(1)如果栈为空,那么缺少与之对应的左括号。(2)如果栈顶元素与之不匹配,那么缺少与栈顶元素...

weixin_33692284的博客 214

Qt图形几何之QRect详解:从基础操作到高级功能全解析

本文全面介绍了Qt框架中的QRect类,详细解析了其核心功能和使用方法。主要内容包括:1)QRect的基本概念与多种构造函数;2)矩形属性获取和状态判断方法;3)几何操作函数(移动、调整大小等);4)几何关系判断(包含、相交等);5)坐标变换和边距处理;6)实用功能与QRectF转换;7)综合应用示例(图形界面、碰撞检测、布局管理);8)性能优化建议和常见陷阱。通过丰富的代码示例,展示了QRect在Qt图形编程中的实际应用,帮助开发者掌握矩形处理的各项操作技巧,提升图形界面开发效率。

专注现代C++实战,从核心语法到高性能编程,助力开发者深入掌握系统级编程精髓。 117

数字逻辑Fundamentals of Digital Logic with Verilog Design | 3rd Edition Solutins Chapter 6(step by step)

第六章6-1~6-42习题详解,缺少6-39和6-40

weixin_62651190的博客 1465

月のLeetCode 每周刷题之 Week1

月のLeetCode 每周刷题之 Week1

sukiki09的博客 319

以 DeleteRestrictions 与 inlineDelete 为核心的 Fiori Elements 表格删除机制全解

SAP Fiori elements 中删除操作的设计与实现涉及多维度考量。框架默认依据 OData 元数据判断删除能力,通过 DeleteRestrictions 注解实现行级条件控制(deletable-path)。表格支持两种删除模式:工具栏集中删除与响应式表格行内删除,但二者不建议混用。关键约束包括:行内删除仅适用于 Responsive Table,且与多选功能互斥。配置需遵循三层架构:后端能力注解、前端表格形态和运行时交互。设计上强调禁用优于隐藏的可用性原则,并需保持列表页与对象页的删除体验一致

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 153

(PTA函数题)求链表的倒数第m个元素

请设计时间和空间上都尽可能高效的算法,在不改变链表的前提下,求链式存储的线性表的倒数第m(>0)个元素。 ElementType Find( List L, int m ); 其中List结构定义如下: typedef struct Node *PtrToNode; struct Node { ElementType Data; /* 存储结点数据 */ PtrToNode Next; /* 指向下一个结点的指针 */ }; typedef PtrToNode List; /*

moreerom的博客 3731

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

习题3.5 求链表的倒数第m个元素 (20 point(s)) 笔记

习题3.5 求链表的倒数第m个元素 (20 point(s)) 原题链接 这道题不难。但我想记录一下这个比较聪明的算法。 下面给出accept代码。 ElementType Find(List L, int m) { List p = L; List q = L; int len = 0; /*while (p->Next != NULL) { len++; p = p-&g...

fire_for_you的博客 476

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

浙大版《数据结构》习题3.5 求链表的倒数第m个元素(20 分)

题目:https://pintia.cn/problem-sets/434/problems/966235055998885888 请设计时间和空间上都尽可能高效的算法,在不改变链表的前提下,求链式存储的线性表的倒数第m(&gt;0)个元素。 函数接口定义: ElementType Find( List L, int m ); 其中List结构定义如下: typedef stru...

云上明月的博客 3029

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

找到链表的倒数第m个元素

设计一个算法,找到链表的倒数第m个元素,假设最后一个元素为倒数第0个。算法的复杂度为n。 方法一:遍历链表,得到链表的长度,再找到链表的第n-m个元素即可(如果链表的长度知道就直接找第n-m个元素)。 方法二:双指针查找。用两个指针p1、p2,开始都指向头节点。p1先移动,当p1和p2的距离为m时,两个指针一起移动,当p1移动到尾节点时,此时p2指向的节点就是要找的节点。 具体代码: #i

skyandcode的专栏 6038

习题3.5 求链表的倒数第m个元素 (20 分)

请设计时间和空间上都尽可能高效的算法,在不改变链表的前提下,求链式存储的线性表的倒数第m(>0)个元素。 函数接口定义: ElementType Find( List L, int m ); 其中List结构定义如下: typedef struct Node *PtrToNode; struct Node { ElementType Data; /* 存储结点数据 */ PtrToNode Next; /* 指向下一个结点的指针 */ }; typedef PtrToNode

weixin_51929568的博客 814

给定一个单向链表(长度未知),请设计一个既节省时间又节省空间的算法来找出该链表中的倒数第m个元素。实现这个算法,并为可能出现的特例情况安排好处理措施。“倒数第m个元素”是这样规定的:当m=0时,链表的

给定一个单向链表(长度未知),请设计一个既节省时间又节省空间的算法来找出该链表中的倒数第m个元素。实现这个算法,并为可能出现的特例情况安排好处理措施。“倒数第m个元素”是这样规定的:当m=0时,链表的最后一个元素将被返回: #include #include #include struct listElementType {     int data;     listEl

JK0803_wantao的专栏 4123
上一篇: 《算法之美》の链表问题のremoveHead中的缺陷
下一篇: 《动态规划》之--字符串比较问题(扩展距离)
ACE1985
博客等级 码龄17年 1307粉丝 308原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值