防御性编程习惯

程序员在编写代码的时候,预料有可能出现问题的地方或者点,然后为这些隐患提前制定预防方案或者措施,比如数据库发生异常之后的回滚,打开某些资源之前,判断图片是否存在,网络断开之后的重连次数或者是否连接备用网络,除法运算中的除数问题,函数或者类在接受数据的时候的过滤情况,比如如果输入一个指针参数,是否需要判断是不是空指针?输入一个字符串参数,是否需要判断字符串空否……总的来说就是防止出现不可预见的事情,设计出鲁棒性的代码。

看下面的例子

输入一个链表,输出链表中倒数第 m 个结点额内容,要求从1开始计数,也就是说,链表的尾结点是倒数第一个结点,例如链表一共有6个结点,从头结点开始:1、2、3、4、5、6.链表的倒数第三个结点的值为4。

typedef struct Node{
int data;
Node *next;
} Node, *List;

思考:

1、最直接的思路就是从链表的末尾开始,回溯到第 m 个结点,但是给出的链表结构里这是单链表,而单链表的 next 指针是从前到后指向的,这个思路是非常不方便的,可以说行不通。

2、那么逆向的不行,就看看正向的,可以把求倒数第 m 个结点转换为求正数第 x 个结点的问题,假设链表有 n 个结点,那么倒数第 m 个结点就是从头开始的正数第 n-m+1个结点,也就是说,知道了表长,然后通过 n-m+1这个数据,就能找到倒数第 m 个结点的位置。也就是需要从头到尾遍历链表,求出表长 n,然后再从头到尾遍历链表,找到 n-m+1个位置即可。代码很容易写出,但是并不是最好的解法。

继续分析第2个思路,求得改进

原思路是需要遍历两次链表的,显然有些臃肿了,那么如果就要求只遍历一次链表就能实现上述要求,就需要思考下改进方法。那就一次定义两个指示指针,让第一个指针和第二个指针的距离相差 m-1个结点就行了,这样,两个指针同步走,当第一个指针走到最后一个结点的时候,第二个指针刚刚走到倒数第 m 个结点的位置。

代码如下:

 //找到倒数第 m 个结点,返回它
Node * getMNode(List head, int m)
{
//第一个指针,在前面遍历链表
Node *forward = head;
//第二个指针,在后面保持距离,然后跟随
Node *behind = NULL;
//先让第一个指针 forward 走 m-1步
for (int i = ; i < m - ; i++) {
forward = forward->next;
}
//第一个指针走到第 m-1位置的时候,behind 指针开始和 forward 保持同步移动
behind = head;
//两个指针开始同步遍历
while (behind != NULL) {
forward = forward->next;
behind = behind->next;
}
//当 forward 遍历到末位的时候,behind 就是要找的位置,倒数第 m 个结点的位置
return behind;
}

继续分析第二个思路,测试代码的鲁棒性

第一、貌似程序的开始,并没有进行判空操作,如果传入的是空链表,也就是 head 是 NULL 指针,如果继续运行,那么代码访问了空指针指向的内存,程序就会发生奔溃。

第二、如果链表的长度 n 比 m 还要小,这样肯定不符合逻辑,需要提前判断和预防(防御性的编程习惯),因为程序里有 for 循环,第一个指针会先走 m-1步,如果n 比 m 小,那么for 循环那里肯定出错,仍是空指针的问题。

第三,这里的函数形参里的 m,声明的是 int 类型,如果声明为了无符号 unsigned int 类型,显然,在fou 循环里,m-1语句有风险!如果 m 本身就是0,则 m-1为-1,类型转换为无符号数,反正肯定不是-1。因此 for 循环的次数不是我们想要的结果。同样程序出现崩亏。

改进的建议

1、如果输入的链表为空表,那么程序直接返回 NULL 处理

2、如果链表程度 n 比 m 小,那么在for 循环语句里,需要if判断一下,查看何时出现 next 等于 null的时候,提前避免空指针错误。

3、对于输入0,代码中函数参数写的是 int 类型,虽然无碍,但是实际上,求倒数第0个结点是没有意义的,因为假定是从1开始的,那么此时也可以返回 null

 //找到倒数第 m 个结点,返回它
Node * getMNode(List head, int m)
{
//进行空表判断
if (NULL == head || == m) {
return NULL;
}
//第一个指针,在前面遍历链表
Node *forward = head;
//第二个指针,在后面保持距离,然后跟随
Node *behind = NULL;
//先让第一个指针 forward 走 m-1 步
for (int i = ; i < m - ; i++) {
//判断是否越界
if (forward->next != NULL) {
forward = forward->next;
}else{
return NULL;
}
}
//第一个指针走到第 m-1位置的时候,behind 指针开始和 forward 保持同步移动
behind = head;
//两个指针开始同步遍历
while (behind != NULL) {
forward = forward->next;
behind = behind->next;
}
//当 forward 遍历到末位的时候,behind 就是要找的位置,倒数第 m 个结点的位置
return behind;
}

继续分析,提炼思想

比如有问题:求链表的中间结点,如果链表结点 n 为奇数,就是返回中间结点,如果结点n 是偶数,那就返回中间结点两个之间的任意一个结点。

思考:

题目是让求中间结点的,那么可以遍历整个链表,求得长度 n,然后判断奇偶性,求得中间结点。其实按照上题的思想,本题同样可以定义两个指针,同时从链表的头结点出发,同步移动,第一个指针 a一次走一步,第二个指针b一次走两步,当走的快的指针到末位结点的时候,走的慢的指针刚刚处在中间位置,(因为a 每次移动都比 b 快1步,而 a 每次走2步,走到了末位的时候,b 其实刚刚走了一半的路程)省去了求n 和判断与计算的过程。

比如一个问题:判断一个单链表是否有环?

思考

有环,说明链表如果一直遍历下去,会回到出发位置。同样,还是定义两个指针,同时从头结点出发,一个指针a一次走一步,另一个指针b一次走两步,如果走的快的b指针反而还追上了走得慢的 a 指针(b 每次两步,如果走到了末位,那么 a 才走到中间,b 继续走就回到了出发点,此时 b 和 a 相距一半的路程,那么 b 继续走,一定会追上 a,且是在起点位置),此时说明链表有环存在,如果走的快的指针走了两圈都没有追上走的慢的a 指针,说明链表没有环结构。

小结:

当处理链表问题的时候,一个指示指针解决不了问题,可以尝试使用两个指示指针,一个走的快点,一个走的慢点,随机应变是同步还是说等待哪个先走,这样可以另辟蹊径去解决问题。

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

防御性编程习惯:求出链表中倒数第 m 个结点的值及其思想的总结的更多相关文章

  1. 找出链表中倒数第 k 个结点

    /* 题目:输入一个单向链表,输出该链表中倒数第 k 个结点.链表的倒数第 0 个结点为链表 的尾指针. 链表结点定义如下: struct node { int data; struct node * ...

  2. 找出链表中倒数第K个结点

    思路:两个指针,也是快指针和慢指针,先让快指针走k -1步,这时慢指针开始和快指针一起走到尾部.慢指针停止的点就是倒数第k个节点. public static ListNode findCountDo ...

  3. 面试经典:链表中倒数第k个结点?如何从大量数据中找出高频词?

    记录两道面试题: 题目描述: 输入一个链表,输出该链表中倒数第k个结点.(单向链表) 拿到这个问题的时候自然而然会想到让链表从末尾开始next   K-1 次不就是第K-1个节点了么,但是必须要注意一 ...

  4. 【剑指Offer面试编程题】题目1517:链表中倒数第k个结点--九度OJ

    题目描述: 输入一个链表,输出该链表中倒数第k个结点. (hint: 请务必使用链表.) 输入: 输入可能包含多个测试样例,输入以EOF结束. 对于每个测试案例,输入的第一行为两个整数n和k(0< ...

  5. 【编程题目】输入一个单向链表,输出该链表中倒数第 k 个结点

    第 13 题(链表):题目:输入一个单向链表,输出该链表中倒数第 k 个结点.链表的倒数第 0 个结点为链表的尾指针.链表结点定义如下: struct ListNode {int m_nKey;Lis ...

  6. 剑指Offer编程题(Java实现)——链表中倒数第k个结点

    题目描述 输入一个链表,输出该链表中倒数第k个结点. 注意: 该题目不可以用先反转链表再输出第k个结点的方式,因为反转链表会改变该结点的next指向 思路一 使用栈Stack倒序存储,顺序pop第k个 ...

  7. 查找链表中倒数第k个结点

    题目:输入一个单向链表,输出该链表中倒数第k个结点.链表的倒数第0个结点为链表的尾指针.链表结点定义如下: struct ListNode { int m_nKey; ListNode* m_pNex ...

  8. 查找单链表中倒数第k个结点

    本文转自:程序员面试题6--查找链表中倒数第k个结点 题目:输入一个单向链表,输出该链表中倒数第k个结点.链表的倒数第0个结点为链表的尾指针.链表结点定义如下: struct ListNode { i ...

  9. 剑指Offer:面试题15——链表中倒数第k个结点(java实现)

    问题描述 输入一个链表,输出该链表中倒数第k个结点.(尾结点是倒数第一个) 结点定义如下: public class ListNode { int val; ListNode next = null; ...

随机推荐

  1. 快速构建H5单页面切换骨架

    在Web App和Hybrid App横行的时代,为了拥有更好的用户体验,单页面应用顺势而生,单页面应用简称`SPA`,即Single Page Application,就是只有一个HTML页面的应用 ...

  2. 【SQLServer】记一次数据迁移-标识重复的简单处理

    汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 今天在数据迁移的时候因为手贱遇到一个坑爹问题,发来大家乐乐,也传授新手点经验 迁移惯用就 ...

  3. ASP.NET MVC5+EF6+EasyUI 后台管理系统(74)-微信公众平台开发-自定义菜单

    系列目录 引言 1.如果不借用Senparc.Weixin SDK自定义菜单,编码起来,工作量是非常之大 2.但是借助SDK似乎一切都是简单得不要不要的 3.自定义菜单无需要建立数据库表 4.自定义菜 ...

  4. H5坦克大战之【画出坦克】

    今天是个特殊的日子,圣诞节,也是周末,在这里先祝大家圣诞快乐!喜庆的日子,我们可以稍微放松一下,扯一扯昨天雷霆对战凯尔特人的比赛,这场比赛大威少又双叒叕拿下三双,而且是一个45+11+11的超级三双, ...

  5. UE4新手引导之下载和安装虚幻4游戏引擎

    1) 进入虚幻4的官方主页(https://www.unrealengine.com/) 这里你可以获得关于虚幻4的最新资讯,包括版本更新.博客更新.新闻和商城等.自2015年起,该引擎已经提供免费下 ...

  6. C#关于分页显示

    ---<PS:本人菜鸟,大手子还请高台贵手> 以下是我今天在做分页时所遇到的一个分页显示问题,使用拼写SQL的方式写的,同类型可参考哦~ ------------------------- ...

  7. 编译器开发系列--Ocelot语言1.抽象语法树

    从今天开始研究开发自己的编程语言Ocelot,从<自制编译器>出发,然后再自己不断完善功能并优化. 编译器前端简单,就不深入研究了,直接用现成的一款工具叫JavaCC,它可以生成抽象语法树 ...

  8. 使用nginx反向代理,一个80端口下,配置多个微信项目

    我们要接入微信公众号平台开发,需要填写服务器配置,然后依据接口文档才能实现业务逻辑.但是微信公众号接口只支持80接口(80端口).我们因业务需求需要在一个公众号域名下面,发布两个需要微信授权的项目,怎 ...

  9. python_单元测试unittest

    Python自带一个单元测试框架是unittest模块,用它来做单元测试,它里面封装好了一些校验返回的结果方法和一些用例执行前的初始化操作. 步骤1:首先引入unittest模块--import un ...

  10. 作为前端er,写在年末的一些话

    写惯了技术类的文章,这种总结和唠嗑型的我基本也就在年末才来一篇.说实话,这种轻松类的文章往往比技术类的要受欢迎的多,因为其受众更广,看起来也不烧脑. 说来愧疚,这一年其实产出有点低,大致就写了不到二十 ...