链表的操作总结

  链表反转

这是一个简单的链表操作问题,在leetcode上面有52.7%的通过率,难度是简单。但是还是想在这里基于python做一下总结,顺便总结一下链表的各种操作。

首先先看一下leetcode上面的题目:

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

看完了题目,很直白,就是转过来。我们尝试对这道题进行解决。这道题用python至少会有三种解决方案。

首先是链表的数据结构定义:

1.将链表遍历进list中,然后利用切片反转list,再将list填充到链表中即可。这是最简单的一种思考逻辑,但是也比较消耗性能。时间和空间复杂度都为O(n)。

2.另一种迭代算法,是一种纯粹交换的迭代,笔者这里截取了leetcode速度最快的一种。

这一波交换操作,我们可以画个示意图就知道他的交换是一种怎么样的交换。

从图中可以看出,循环的作用就是将反向指针进行保存。同时令将指针转向的功能。

3.最后一种方案是采用递归的方式进行链表反转。这种方式也需要一定的理解。我们先展示一下代码。

这种解法其实理解起来只有两部分内容,传递反向指针和进行指针反向拼接。我们先来理解一下指针反向拼接这个操作。

如此循环即可将链表反转过来。但是还有个关键就是将最后一个指针传递出来。我们可以看到之前的代码中,end传出来后是一直没有做任何操作的。不停的return出最后一个指针。所以就将最后一个指针传递了出来。
以上就是链表反转的3中方法。除此之外还想写一些链表的简单操作。

  快慢指针

何为快慢指针,即对链表进行两个不同步频的指针标记遍历。最经典的是慢指针走一步,快指针走两步。

快慢指针有很多的应用,比如说:

1.判断一个链表是否存在环。

两个指针并排走,如果有环的话快指针最终会指向慢指针的位置。没有环的话,快指针会指向None后退出。
当然这道题的解法不止这一样,还可以利用set进行判断。
2.输出链表中的倒数第K个节点
这道题利用快慢指针的思路是这样的。定义两个指针,第一个指针向前走k-1步;第2个指针保持不动;到第k步时第2个指针也开始移动。由于两个指针始终保持着k-1的距离,所以当快指针到达末尾时,慢指针刚好指向倒数第k个。

链表的技巧总结

  技巧一 :理解指针或者引用的含义

看懂链表结构不是很难,但是一旦把它和指针混在一起,就很容易让人摸不着头脑,(我再写代码的时候就出现了这种情况),所以要想写对链表代码,首先要理解好指针。
实际上,对于指针的理解,你只需要记住下面这句话就可以了:将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。

  技巧二:警惕指针丢失和内存泄露

在写链表代码的时候,经常会找不到指针(引用)指到了哪儿,会弄丢了指针。
例如在单向链表中插入节点x,前节点是p,后节点是b。

p.next = x ;
x.next = p.next;

这样就会导致指针丢失和内存泄露。如果把两行代码的顺序颠倒一下,就不会丢失指针,要先将x.next指向b,然后,在将p的next节点指向x,这样才不会丢失指针导致内存泄露。

  技巧三:利用哨兵简化实现的难度

哨兵,解决的是国家之间的边界问题。同理,这里说的哨兵也是解决"边界问题"的,不直接参与业务逻辑。(还不是很了解,之后再补充)。
技巧四:重点留意边界条件处理
软件开发中,代码在处理一些边界问题或者异常情况中,容易出现bug。链表代码也是容易产生bug,要想实现没有bug的链表代码,一定要在编写的过程中以及在编写完之后,检查边界条件是否考虑的全面,以及代码在边界条件下是否能正常运行。
经常用来检查链表代码是否正确的边界条件有如下几个:

  • 如果链表为空,代码是否能正常运行?

  • 如果链表只包含一个节点时,代码是否正常运行?

  • 如果链表只包含两个节点时,代码是否正常运行?

  • 代码在处理头节点和尾节点时,代码是否正常运行?

我们在写代码的时候也不要只是实现业务逻辑就完事,也要多考虑会遇到哪些边界问题或者异常情况,遇到了应该如何解决,这样写出来的代码才会健壮。

  技巧四、留意边界的处理

比如链表为空、比如只包含一个结点或两个节点的情况。比如处理头结点和尾节点时,代码是否正确。

再就是多写多练了,这里给出java语言实现的链表代码。

  技巧五:举例画图,辅助思考

举例画图,这个太有用了,我再学习算法的过程中,有时候看不懂实现代码的时候,就想着如何通过画图来分解代码的实现步骤,感谢在学习中,帮助过我的朋友,让我能够理解实现的过程。
在这里继续用上述的有序链表的合并的代码:

画图辅助思考:
我们可以看到这个代码里就体现了边界问题的几个步骤:

1、首先是链表为空时的处理。

2、链表头的处理。

3、链表多节点的处理。

4,链表尾的处理。

而图中的举例也正是对代码实现的分解的很好的解释。(画图辅助真的很棒)

  技巧六:多谢多练,没有捷径

就是把常见的链表操作都自己多写几遍。最开始我都是遇到了各种各样的不理解,甚至对于链表的操作都有些迷糊,但是多出问题多调试,慢慢的我们也能孰能生巧。唯手熟尔!
常见的链表操作,只要能熟练的写出来,不熟就多写几遍,保证之后就不会再害怕写链表代码。

  • 单链表反转

  • 检测链表中的环

  • 两个有序链表的合并

  • 删除链表中倒数第K个节点

  • 球链表的中间节点

写出正确链表代码的六个技巧。分别是理解指针或引用的含义、警惕指针的丢失和内存泄露,利用哨兵简化实现难度,重点留意边界条件处理,以及举例画图,辅助思考,还有就是多写多练,唯手熟尔。
勤能补拙,生活就是养成游戏,勤练内功,即使当前不能花里胡哨,未来也会强壮到无人能敌!

链表的经典技巧及算法

  1、寻找链表的中间节点:最简单的方法是,先遍历一遍链表,计算出链表的长度,然后计算出中间节点的位置,然后再遍历一遍,边遍历边计算,直到找到中间节点,这个方法略显啰嗦,最坏的情况需要遍历2次链表,代码如下:

另一个更灵巧的方法是,用两个指针,慢指针每次走一步,快指针每次走两步,当快指针走到链表的末端(NULL)时,慢指针正好指向了中间节点,代码如下:

  2、检测链表是否有环:经典的做法也是用快慢指针,如果没有环,快指针一定先到达链表的末端(NULL),如果有环,快、慢指针一定会相遇在环中,代码如下:

  3、检测环的入口:经典的做法是先检测是否有环,如果有环,则计算出环的长度,然后使用前后指针(不是快慢指针),所谓的前后指针就是一个指针先出发,走了若干步以后,第二个指针也出发,然后两个指针一起走,当前后指针相遇时,它们正好指向了环的入口,代码如下:

如果允许使用额外的内存,可以有更简单的做法,即一边遍历,一边将节点放在map中,当某个节点第二次出现在map中时,它就是入口节点,代码如下:

  4、链表翻转:假设原链表为1->2->3,翻转以后的链表应该是1

  5、删除链表中的节点,注意这里只给出要删除的那个节点,不给出链表头(假设被删除节点不是尾节点),代码如下:

如果被删除节点是尾节点,上面的代码就无法将target上一个节点的next置为NULL,所以只有给了头节点后,才能遍历到target的上一个节点,并把其next置为NULL。  6、回文链表的检测:所谓回文链表,即链表元素关于中间对称,,如1->2->3->2->1,比较简单的方法是用一个栈,先顺序遍历链表,并把每个节点放入栈中,遍历完成后,栈的出栈顺序正好是原链表的逆,代码如下:

上面代码的空间复杂度为O(N),其实还有空间复杂度为O(1)的算法,也很灵巧,运用了之前提到的一些技巧,代码如下:

这个方法的缺点是修改了原链表,但是综合运用了链表的很多技巧,值得收藏。

  7、合并有序链表:基本思路跟合并有序数组一样,但是不需要O(N)的空间复杂度了,只需要O(1)的空间复杂度,代码如下:

其实如果不要求空间复杂度为O(1),可以用递归的思想,代码更简略,如下:

  8、链表排序:如果没有空间复杂度、时间复杂度的要求,那可选的方法太多了,像插入排序、选择排序、冒泡排序,但是如果要求时间复杂度为O(NlogN),而且空间复杂度为O(1)呢?归并排序!!!正好可以用上刚刚写的合并有序链表的代码,代码如下:

  9、链表的循环右移:举例如下1->2->3->4->5->NULL,循环右移2位后,变成了4->5->1->2->3->NULL,可以这么考虑,如果链表的长度为N,循环右移K位,那么等效于循环右移 K%N位,K%N是一个小于N的数,然后我们只需要找到循环右移后的头节点即可,上面的例子就是4,然后直接把1->2->3链接到4->5->的后面,代码如下:

  10、以组为单位翻转链表:组的长度用K表示,比如原链表为1->2->3->4->5,当K=2时,翻转的结果为2->1->4->3->5,当K=3时,翻转的结果为3->2->1->4->5,即先翻转K个,再翻转K个,当剩下的节点数小于K时,就不用翻转了。用递归的方法很容易实现,代码如下:

这个算法的空间复杂度为O(N/K),即正比于递归深度,如果要求空间复杂度为O(1)呢?其实也比较简单,只要循环处理每一段长度为K的链表,处理的时候注意保存上一段链表的尾节点,代码如下:

11、翻转链表的相邻节点,比如原链表为1->2->3->4,翻转后为2->1->4->3,这个其实就是上一道题的特例,即K=2,也要求空间复杂度为O(1),不过还是递归简洁啊,这里只给出递归的代码:

  12、删除链表的倒数第N个节点,要求只遍历一次,还记得检测环的入口吗?是的,用前后指针,前后指针需要相隔(N+1)步,这样当前指针为NULL的时候,后指针正好指向倒数第(N+1)个节点,然后直接删除倒数第N个节点即可,代码如下:

  13、删除有序链表中的重复元素,如原链表为1->2->3->3->4->4->5,删除后的链表为1->2->5,这道题的关键是如果某节点有重复,务必将其全部删掉,所以要对有重复的节点做个标记,代码如下:

技巧:哑变量的引入使得头结点不再具有特殊性,从而简化处理流程。  14、像快排那样将链表分成前后两个部分,比如原链表为1->4->3->2->5->2,给出数字3,那么链表中比3小的放在前面,比3大(或等于3)的放在链表的后面,处理后的链表应该是这样的1->2->2->4->3->5,注意,4和5都大于3,那么处理后的链表中4仍然应该在5的前面,代码如下:

  15、合并K个有序链表,这个咋一听很简单,先合并第1个、第2个,然后将合并后的结果与第3个合并,然后将合并的结果与第4个合并……,假设每个链表的长度为N,那么时间复杂度为O(NK²),N为总的节点数,因为要合并K次。其实有更优的时间复杂度,我们可以先两两合并,即第1个与第2个合并,第3个与第4个合并,即执行K/2次合并,这作为第一轮合并,时间复杂度为O(KN),接下来就只需要合并K/2个有序链表了,即进行第二轮合并,这样总共需要进行logK轮合并,每一轮的时间复杂度为O(KN),所以总的时间复杂度为O(NKlogK),代码如下(合并两个有序链表的代码已经在前面给出):

  16、寻找两个链表的第一个公共节点,即两个链表从某个节点开始合并了,我们要找出这个节点,经典的方法是先计算两个链表的长度:L1,L2,假设L1>L2,那么公共节点一定不在链表1的前(L1-L2)个节点中,这样我们就可以让链表1的头节点指向第(L1-L2+1)个节点,然后同时推进两个链表的头节点,边推进边比较,直到遇到同一个节点,代码如下:

  17、链表的插入排序,这个就很直白了,代码如下:

  18、把有序链表转换成一个尽量平衡的二叉树,其实所谓的尽量平衡,就是把链表的中间节点作为根节点,根节点左边的链表是树的左子树,根节点右边的链表是树的右子树,然后问题就转换成了原问题的子问题,即用前半段链表建立一个尽量平衡的左子树,用后半段链表建立一个尽量平衡的右子树,代码如下:

  19、在链表上模拟加法运算,比如(2 -> 4 -> 3) + (5 -> 6 -> 5)=7 -> 0 -> 9,链表的头节点为个位,然后是十位……其实模拟的关键就是处理进位,代码如下(略长,但比较直白):

信息整理自网络

实践出真知▼

写出一段代码将链表中的两个节点位置互换位置_干货||链表的技巧和算法总结...相关推荐

  1. 通过一趟遍历找出长度为n的单链表中值最大的节点.【数据结构】【单链表】

    编写一个函数完成如下功能:通过一趟遍历找出长度为n的单链表中值最大的节点. 要求,在主函数中调用上面的函数测试. 提示:还需要定义其他函数,比如初始化链表,构造单链表,输出单链表. 输出结果: 代码展 ...

  2. 写出一段代码将链表中的两个节点位置互换位置_面试 leetcode 算法专题系列(二)—— 链表...

    前言:只照着常考题去刷题确实是一种方法.但调研之后发现自己还是考虑不周,刷题刷的不应该是题,而是解题的思路和熟练程度.于是我决定重新组织一下刷题笔记的讲解顺序,不再以面试常考题来刷.而是以面试出题频率 ...

  3. 链表中每两个节点交换位置

    LeetCode上的一道两两交换链表节点的题目:如下 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表. 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换. 示例: 给定 1-&g ...

  4. 请写出一段 python 代码实现删除一个 list 里面的重复元素

    请写出一段 python 代码实现删除一个 list 里面的重复元素 方法一:利用set集合实现 info = [2017,1,16,9,2017,1,16,9] result = list(set( ...

  5. Java中有关日期的操作,昨天晚上赴约,搞到12点多才回来,今天写这一小段代码都花了一段漫长的时间,哎。。...

    Java中有关日期的操作,昨天晚上赴约,搞到12点多才回来,今天写这一小段代码都花了一段漫长的时间,哎.. 代码奉上: /** * * @param date * @return which mont ...

  6. 如何写出漂亮的代码:七个法则

    如何写出漂亮的代码:七个法则. 首先我想说明我本文阐述的是纯粹从美学的角度来写出代码,而非技术.逻辑等.以下为写出漂亮代码的七种方法: 1, 尽快结束 if语句 例如下面这个JavaScript语句, ...

  7. python用什么软件编程1001python用什么软件编程-怎样才能写出 Pythonic 的代码 #P1001#...

    L = [ i*i fori inrange(5) ] forindex, data inenumerate(L, 1):print(index, ':', data) 去除 import 语句和列表 ...

  8. python好学吗1001python好学吗-怎样才能写出 Pythonic 的代码 #P1001#

    L = [ i*i fori inrange(5) ] forindex, data inenumerate(L, 1):print(index, ':', data) 去除 import 语句和列表 ...

  9. 如何写出健壮的代码?

    简介:关于代码的健壮性,其重要性不言而喻.那么如何才能写出健壮的代码?阿里文娱技术专家长统将从防御式编程.如何正确使用异常和 DRY 原则等三个方面,并结合代码实例,分享自己的看法心得,希望对同学们有 ...

最新文章

  1. mongodb创建用户名和密码_Python中使用MongoDB详解
  2. 如何在vscode中使用GitLab
  3. Warning:java: 来自注释处理程序 'org.antlr.v4.runtime.misc.NullUsageProcessor' 的受支持 source 版本 'RELEASE_6' 低于
  4. Linux忘记root密码怎么办?
  5. 深度学习-函数-tf.nn.embedding_lookup 与tf.keras.layers.Embedding
  6. springboot+flowable第二节(流程基本操作)
  7. 优秀!腾讯AI Lab开源模型压缩与加速框架PocketFlow!
  8. 让你难忘的一段情感故事是什么?
  9. 64位CentOS源码编译方式安装wine
  10. 国外变电站3d可视化技术发展_盘点:测量技术五大发展趋势,含3D扫描
  11. JCMsuite应用:闪耀光栅
  12. java分享微博_java_java实现的新浪微博分享代码实例,weibo.java {@link IWeiboShareAPI#handle - phpStudy...
  13. CPU(中央处理器)和GPU(图像处理器)区别
  14. 线程状态的区别 blocked waitting ,join 详解
  15. 三级分销之父徐张生:人人店引领微商界版“裂变”
  16. vue mounted遇到的问题
  17. QQ空间中的日志在不同用户的主页显示不同QQ号方法
  18. nginx和openresty配置静态资源时,样式错乱
  19. python生成静态html_python – 从XML内容生成静态HTML站点
  20. QAbstractSlider、QSlider、QDial、QScrollBar

热门文章

  1. 微软远程桌面8.0汉化版
  2. Docker服务开启TCP端口
  3. 《名贤集》《明贤集》七言集
  4. 面试官爱问Spring初始化?别急,看完这篇文章,咱去吊打他
  5. 到底死不死我就请了七天假_“爹,你到底死不死,我就请了七天假。”
  6. ADI Blackfin DSP处理器-BF533的开发详解15:RS232串口的实现(含源代码)
  7. css 粘性布局吸顶position:sticky与js监听滚动手动实现
  8. C5.0决策树建立个人信用风险评估模型
  9. Shutdown定时关机器源码
  10. linux运行go程序命令行,宝塔面板Linux环境-安装Golang:Go语言环境安装以及程序如何运行...