链表
1、两大方法
1.1、迭代法(使用指针)
因为链表可以使用指针进行遍历,这时块就可以使用指着对链表进行遍历
可以使用双指针,快慢指针
1.2、递归法
链表具有天然的递归性
可以把上图中的第二个链表看成节点 0 后面挂接了一个更短的链表,比第一个链表少了一个节点;
这个更短的链表可以看成 1 作为头节点的链表,这个更短的链表可以继续看成节点 1 后面挂接了一个更更短的链表;
这个更更短的链表可以看成 2 作为头节点的链表,依次类推,空本身可以理解也是一个链表,最基础最平凡的链表。
有了这样的思考,链表中的很多操作,都可以使用递归这种逻辑思维方式来完成。
2、解题技巧
2.1、利用快慢指针(有时候需要用到三个指针)
- 链表的翻转
- 寻找倒数第k个元素
- 寻找链表中中间元素
- 判断链表是否有环
2.2、构建一个虚假的链表头 哑节点方便进行输出尤其牵扯到链表的头部节点需要进行变动时**
- 两个排序链表,进行排序整合
- 将链表的奇偶数按原定顺序分离,生成前半部分为奇数,后半部分为偶数
2.3、删除链表节点
记录删除节点的前一个节点
3、如何训练该技巧
在纸上或者白班上画出节点之间的互相关系画出修改的方法
凭空想象是比较困难的
画在白班上,还能让面试官清楚的看到思路
4、习题总结与分类
4.1、找到链表中的某个节点
-
链表的中间节点:快指针与慢指针,快指针一次走两个节点,慢指针一次走一次节点,当快指针走到链表尾部时,慢指针走到链表的中间节点
-
链表的倒数第k个节点:快指针先走
k
步;然后快慢指针一起走,当快指针走到尾部时,慢指针走到倒数第k个节点
-
链表中下一个更大节点:
方法1
:暴力法遍历整条链表在其后面找到每个链表大于它的值;方法二
:可以使用栈,单调栈,从前遍历节点,对于每个节点,与栈中所对应的值进行比较,若大于栈中顶的值则将栈中元素全部出栈,栈中元素存储索引所对应的值的下一个更大值为当前遍历的节点
,
4.2、删除链表中的节点
最好采用
哨兵机制
,定义一个哑节点dummyNode
,令dummyNode.next=head
删除节点需要保存待删除节点的前一个节点cur
,因此往往在遍历的时候,需要保存前一个节点
递归与迭代的方式都可以试试
1、递归的方式不用考虑过多的细节
2、迭代的方式需要考虑太多的细节
-
移除链表中某个特定的值:
因为可能吧头节点删除,可以定义一个哑巴节点指向头节点
;定义一个cur节点遍历整个链表
,pre保存遍历节点的前一个节点,方便删除
;发现cur.val=val
则删除pre.next=cur.next; cur =cur.next
,否则pre=pre.next, cur=cur.next
-
移除链表中的重复节点(链表未排序):
可以暴力的双重循环,对链表中的每个节点进行判断;
;进行改进:定义cur
节点遍历整个链表,定义HashSet
保存已经遍历链表中不重复的元素,若发现cur
在链表中已经存在则将cur
节点从链表删除,否则将cur
节点加入到链表中 -
移除链表中的重复节点(链表已经排序):使用
双指针
,pre
,cur
若pre.val=cur.val
则pre.next=cur.next,cur=cur.next
,否则pre=pre.next,cur=cur.next
-
移除链表中的重复节点(链表已经排序):
双指针
,和上提的区别就是把所有的重复元素都删除,一个都不保留。
4.3、分析链表的结构
在链表的中间节点,链表的反转,链表的快慢指针,双指针的基础上进行整合,分析
- 链表是否有环:
快慢指针
,`块指针一次走两步,慢指针一次走一步,若有环则必然有相遇的时候,若无环则快指针会很快走到链表的尾部 - 链表环的入口:在上提的基础上进行,
关键是:发现规律,找到第一次相遇的节点,之后令一个节点指向头节点,相遇节点与头结点不断向后走,载次相遇则为环的入口(具体看连接中的图)
- 判断两条链表是否为相交链表:
定义两个指针
,从两个链表中分别向前走,若走到自己链表的尾部则接着走另外一条链表,若最后两条指针相遇则说明存在交点 - 回文链表:回文的特点是从前往后和从后往前读的顺序一样。可以使用
快慢指针法
找到链表的中间节点;将中间节点之后的节点进行反转
;则此时遍历中间节点前链表和中间节点之后已反转的链表是否相等则可
4.4、更改链表的结构(链表的合并,链表位置的调整,链表的反转)
- 注意保存可能用到的节点,因为往往需要修改大量节点,
是否可以采用迭代法
通常定义哑节点方便返回
思考是否可以用递归法
递归法
迭代法
- 反转链表:定义两个指针
reverSeTop(已经反转链表的首部)
,nonReverseTop未反转链表的首部
,不断向后遍历,令ListNode next = nonReverSerTop.next; nonReverseTop.next=reverSeTop; reveseTop=nonReverSeTop;nonReverSeTop=next
,在遍历链表的时候进行节点指针的更改实现反转 翻转链表的前k个节点
- 反转链表中的某些节点m-n:`在上提的基础上,先找到m节点,然后进行反转
- 合并两个有序链表:
方法1
:定义两个节点分别指向两个链表的首部,不断比较连个节点当前的值,添加到新链表的后面
,当一条链表遍历完成后,则可以将为遍历完的链表直接添加到新链表的后面;方法2:采用递归: 终止条件:两条链表分别名为 l1 和 l2,当 l1 为空或 l2 为空时结束; 本级递归内容:如果 l1 的 val 值更小,则将 l1.next 与排序好的链表头相接,l2 同理
参考 - 两两交换链表中的节点:
方法1
保存多个节点,遍历整个链表,调节指针;方法2:递归法
- 将链表分为奇数与偶数链表:
将奇节点放在一个链表里,偶链表放在另一个链表里。然后把偶链表接在奇链表的尾部。
- 分隔链表:
将链表平均分
- 旋转链表:
注意链表的旋转的特点是具有周期性的,因此若链表长度为n,旋转k次则等价于旋转k%n次
;旋转可找到链表的倒数第k个节点,将从倒数第k个节点指向链表的头部,倒数第k-1为尾部节点即可
;循环旋转,但其实本质上是将尾部向前数第K个元素作为头,原来的头接到原来的尾上 - 链表中两数相加返回新链表:
定义两个指针,遍历两个链表,计算和并确定是否需要进位
4.5、链表的排序
步常用,用到数组排序的思想,归并,选择排序
哑节点的技巧
需要保存其前面的节点
- 选择排序:
遍历链表,对链表中的每个接点,从链表的头部开始确定应该插入的位置,确定插入位置时,移动指针即可