所有的链表题目都注意要考虑空指针的问题,当节点p=nullptr时,如果使用了p->next或p->val就会出现异常错误,导致代码无法运行
【1】反转链表 相关题目
反转链表是最基础的链表题目,过程很简单,只要在纸上或者脑内成功模拟,基本都可以写对。
题目(1):JZ24 反转链表
完整解析:https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca
二刷心得: 此题是将一个链表进行完全反转,所以需要额外设置一个pre节点以及一个ne节点辅助记录指针位置,同时要在脑内模拟好反转过程,否则很容易写错,几个指针移动的过程是固定的不能换位置。
问:在完全掌握反转整个链表后,考虑如何反转指定区间的链表?
解法1:需要记录待反转区间的前一个节点pre,后一个节点ne,将该区间取出来之后反转,再用pre和ne拼回去
解法2:固定子区间外的节点,遍历翻转区间内的每个节点,利用头插法的思想不断地将新节点移到反转区间起始位置(因为头插法创建链表后和输入顺序相反),而起始位置需要反转区间的前一个节点pre来确定。
题目(2): BM2 链表内指定区间反转
两种解法的完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/122463151
二刷心得: 无论使用哪种方法,都一定要理解指针移动的顺序,什么时候跳出循环以及跳出循环后的状态(一定要画图模拟),否则很容易做错。如解法二中遍历翻转区间内的每个节点循环次数为n-m(n-m+1个节点只需要n-m个节点头插过去)
问:在完全掌握部分反转链表后,考虑如何将给出的链表中的节点每 k 个一组翻转?
此题较以上两道题更难一些,需要画图模拟几遍
(1)指定上题对于区间反转,使用了投机取巧的方法,先断开连接再反转,再连接。但是此题解决了反转区间前后有节点的问题,解决办法是修改反转函数+递归
(1)每k个一组进行翻转和完全反转类似,完全反转需要初始化前驱pre=nullptr,区间a到x之间的反转,需要初始化前驱pre为区间尾x的下一个节点
题目(3): BM3 链表中的节点每k个一组翻转
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/121109103
二刷心得: 我认为此题最难的部分有两点:一是为什么反转函数Reverse_a_b(a,b)传入的参数是b,也就是初始化前驱pre为b的原因;二是使用递归进行更新各组反转时,需要在向上返回的过程中对链表进行重新连接。一定要理解好这两点。
【2】链表中的环 相关题目
链表中的环相关题目也有很多变形题目,判断是否有环是最基础的一个,方法有很多种,最容易想到也是最常用到的思想有两个:
(1)遍历链表时用map记录这个节点有没有被访问过,如果同一节点被访问两次则存在环。
(2)用快慢指针遍历链表,如果两个指针相遇则存在环,直到遍历完也没相遇则不存在。 需要注意的是,一定要先移动fast、slow指针再判断是否相遇
题目(4):BM6 判断链表中是否有环
两种解法的完整解析:https://editor.youkuaiyun.com/md/?articleId=121043375
二刷心得: 题目和代码很简单,但这两种思想会贯穿在很多题解中,需要熟练掌握。
此题是针对于上一个题延伸问题,当链表存在环时其入口结点为?根据上一题的两种,解法也有两个
(1)遍历链表时用map记录这个节点有没有被访问过,第一个出现两次的节点为入口。
(2)用快慢指针遍历链表,通过计算得出fast、slow相遇位置到入口距离=pHead到入口的距离,所以此时让slow和fast分别从相遇位置和起点 以相同速度出发,再次相遇时的位置即为入口。
题目(5): JZ23 链表中环的入口结点
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/121108156
二刷心得: 需要理解为什么fast、slow相遇位置到入口距离=pHead到入口的距离,以及特判的条件。
【3】快慢指针解法 相关题目
回文结构就是完全对称的结构,但是由于链表不能倒着遍历,所以不能直接利用索引遍历
解法1:将后半段链表进行反转,再与前半段链表进行逐个判断,来确定其是否是回文结构
解法2:利用栈的先进后出特点,将前半段放入栈内,再出栈与后半段逐个判断。
题目(6): BM13 判断一个链表是否为回文结构
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/121894471
二刷心得: 两种方法都要将链表划分为前后两段,所以我们就要找到链表的中点,找中点的方式可以使用快慢指针。需要注意的是当链表长度为奇数时,中间的节点不参与反转。
题目(7): JZ22 链表中倒数最后k个结点
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/123466001
二刷心得: 使用快慢指针,先让快指针走k步,然后slow、fast按相同速度走,当fast=nullptr时,slow到达倒数第k个位置
【4】删除链表的节点 相关题目
删除链表中的节点也是基础中的基础,一定掌握好具体的步骤
题目(8): JZ18 删除链表的节点
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/121131864#1_1
二刷心得: 当删除的节点可能是第一个节点时,要考虑添加虚拟头节点
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
题目(9): BM15 删除有序链表中重复的元素-I
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/121131864#2NC25_I_41
二刷心得: 当出现重复元素时,要比较当前节点p和下一个节点p->next的值,如果相等则p->next=p->next->next
(此时不移动p,以防后续节点依然是重复的);如果不相等才移动p
给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。与上题不同之处在于:上一题要如果出现重复的元素那么只留一个,此题如果出现重复的元素那么一个都不留,要删除所有重复的元素
题目(10): BM15 删除有序链表中重复的元素-II
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/121131864#3NC24_II_73
二刷心得: 要想保证所有的重复元素都被删除,要用到两个指针,一个pre记录已经放在答案链表中的最后一个节点,一个cur用来找下一个可以放入的节点。当cur的值=next的值,要修改cur的next指针以判断下一个是否也是重复的:cur->next=cur->next->next
。当cur的值!=next的值时,判读cur是否是重复节点:如果是则跳过该节点pre->next=cur->next;
如果不是则将其加入答案链表pre=next;
同时要注意空指针的问题,当要用cur->next对节点赋值时(如cur=cur->next;
),在外面的while循环中就必须判断cur->next!=nullptr。此外此题在结束时如果最后一个节点为重复节点,pre未被赋值就会跳出循环,所以要再次赋值。
【5】合并链表 相关题目
合并两个有序链表是一个非常基础的题,下面的递推解法会被经常使用到,所有必须要熟练掌握
题目(11): JZ25 合并两个排序的链表
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/121235293
二刷心得:
- 对于递推方法: 合并两个链表需要新建一个虚拟头节点作为结果链表,每次循环取两链表中小的那个进行连接,当循环结束时将剩余的有序链表接到结果链表中即可。
- 对于递归方法: 有序链表的合并可以表示为,不断地取两链表中最小的那个记作cur,再将其连接起来。当取到了cur,其next指针应该指向哪个呢?——指向链表1链表2中小的那个节点,而这个过程又和上面的步骤极其相似。所以我们将找两链表中最小节点的部分写成递归,当递归到结尾向上返回的时候(返回的是小的表头),这时cur的next指针就确定了。
我们通过上面的题已经知道如何将两个有序链表进行合并(这个功能可以抽取为一个基础函数),下面这个题是对上题的延伸,需要合并k个有序链表,这k个有序链表存储在vector数组中,返回的是最终合并后的链表头。
解法1: 先将2个链表进行合并,通过返回的链表再与下一个进行合并,如此往复直至数组中所有的链表都被合并。
解法2: 可以将数组中各元素从中间拆分为一半一半,直至最后只剩两个链表,此时再进行合并,这里用到的思想就是分治。我们需要设置两个函数,一个完成合并两个有序链表,一个完成分治的合并排序并返回合并后的新链表。
题目(12): BM5 合并k个已排序的链表
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/122610550
二刷心得: 使用解法1对两个链表进行合并的时候,需要考虑特殊情况否则会被卡住:当数组为空时返回nullptr;当数组中只有一个链表时返回第一个链表(无需合并)。使用解法2时,继续使用合并两个链表的函数,此外对于分治合并函数需要理解MergeTwo(Merge(lists,l,mid), Merge(lists,mid+1,r));
的含义:先对数组中的链表进行二分,直至只剩下一个此时返回,递归返回的时候是对两个链表进行合并。
【6】链表的深拷贝
题目(13): JZ35 复杂链表的复制
完整解析: https://blog.youkuaiyun.com/qq_46126258/article/details/123490266
二刷心得:
- 使用解法1时,用一个map记录原节点-新节点的的映射m,那么新节点的random指针指向xx->random=m[x->random]。第一次遍历原链表,创建与原链表等价的节点,并按序将新节点进行连接,同时记录原链表节点到新节点的映射表m;第二次遍历映射表,通过映射表对新节点的random指针的指向赋值。
- 使用解法2时,在第一次遍历原链表时创建新节点,将其插入在原结点后面;第二次遍历链表时对random指针赋值;第三次遍历遍历链表时将新节点拆出来。需要注意的是在对指针赋值和拆链表时要判断要使用的节点是否为nullptr,否则会出现空指针异常。