• 数据结构:链表(2),链表面试题


    203. 移除链表元素 - 力扣(LeetCode)

    给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

    示例 1:

    输入:head = [1,2,6,3,4,5,6], val = 6
    输出:[1,2,3,4,5]
    

    示例 2:

    输入:head = [], val = 1
    输出:[]
    

    示例 3:

    输入:head = [7,7,7,7], val = 7
    输出:[]
    

    提示:

    • 列表中的节点数目在范围 [0, 104] 内
    • 1 <= Node.val <= 50
    • 0 <= val <= 50

    就是我上一篇博客中提到的removeallkey方法

    数据结构:链表(1)_cx努力编程中的博客-CSDN博客


    206. 反转链表 - 力扣(LeetCode)

    给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

    示例 1:

    输入:head = [1,2,3,4,5]
    输出:[5,4,3,2,1]
    

    示例 2:

    输入:head = [1,2]
    输出:[2,1]
    

    示例 3:

    输入:head = []
    输出:[]
    

    提示:

    • 链表中节点的数目范围是 [0, 5000]
    • -5000 <= Node.val <= 5000

    有些人拿到题目第一反应:欸,我可以创一个列表,把这些节点的值拿出来扔到列表里,然后再反向打印不就行了

    nonono,马老师曾说过,不要搞耍小聪明。这样虽然列表转了,但是链表还是那个样,题目要求的是链表的反转,没转的话还是过不了~

                                                       👇

    第一步:将头节点设为null

    第二步:拿第二个节点开始进行头插法

    0x81位置改成0x85

    第三步:继续拿第三个和最后一个节点前插

    这样不就反转了吗🙂

    设置curNext 保存当前节点 cur 的下一个节点的引用,防止在修改 curnext 指针后丢失对下一个节点的引用


    876. 链表的中间结点 - 力扣(LeetCode)

    给你单链表的头结点 head ,请你找出并返回链表的中间结点。

    如果有两个中间结点,则返回第二个中间结点。

    示例 1:

    输入:head = [1,2,3,4,5]
    输出:[3,4,5]
    解释:链表只有一个中间结点,值为 3 。
    

    示例 2:

    输入:head = [1,2,3,4,5,6]
    输出:[4,5,6]
    解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
    

    提示:

    • 链表的结点数范围是 [1, 100]
    • 1 <= Node.val <= 100

    第一种方法:

    1.先求整个链表的长度

    2.再求长度/2 就能找到中间节点

    🆗代码满上 demo

    1. public ListNode middleNode(){
    2. int len = size();
    3. ListNode cur = head;
    4. for (int i = 0; i < len/2; i++) {
    5. cur = cur.next;
    6. }
    7. return cur;
    8. }

    第二种方法:

    我们可以设置两个指针,一个叫fast,一个叫slow,这两个指针在链表上面赛跑,从head开始,fast每次跑两个节点,slow每次跑一个节点

    对于偶数个节点的链表来说,当fast跑出链表的时候,slow刚好停在第二个中间节点

    此时fast = null

    对于奇数个节点的链表来说,fast跑到链表末端时,slow刚好停在中间节点处

     此时 fast.next = null

    这种方法是上一种2倍快


    链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)

    描述

    输入一个链表,输出该链表中倒数第k个结点。

    示例1

    输入:

    1,{1,2,3,4,5}

    返回值:

    {5}

    第一步:fast走k-1步

    第二步:两个指针一块走,直到fast走到末端

     此时的slow正好处在倒数k=4的位置上

    细节问题:

    1.k不合法 比如-1,0,超过链表长度

    2.第一版写法

    1. public int size() {
    2. int count = 0;
    3. ListNode cur = this.head;
    4. while (cur != null) {
    5. count++;
    6. cur = cur.next;
    7. }
    8. return count;
    9. }
    10. public ListNode FindKthToTail(ListNode head,int k) {
    11. if(k <= 0||k>size()){
    12. return null;
    13. }
    14. ListNode slow = head;
    15. ListNode fast = head;
    16. while(k-1!=0){
    17. fast = fast.next;
    18. k--;
    19. }
    20. while(fast.next != null){
    21. fast = fast.next;
    22. slow = slow.next;
    23. }
    24. return slow;
    25. }

    有没有办法可以不写k>size()呢?因为还得去定义一个新的size函数去记录链表的长度

    假设我们的k=4

    当我们执行完第一个循环之后,fast直接为空了,后面第二个循环会有空指针异常

    那我们直接在第一个循环就把这个k给判断了

    改完后的完整代码


    21. 合并两个有序链表 - 力扣(LeetCode)

    将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

    示例 1:

    输入:l1 = [1,2,4], l2 = [1,3,4]
    输出:[1,1,2,3,4,4]
    

    示例 2:

    输入:l1 = [], l2 = []
    输出:[]
    

    示例 3:

    输入:l1 = [], l2 = [0]
    输出:[0]
    

    提示:

    • 两个链表的节点数目范围是 [0, 50]
    • -100 <= Node.val <= 100
    • l1 和 l2 均按 非递减顺序 排列

     假设我们要将这两个链表合并了 

     

    我们可以这么做:申请一个虚拟节点,这个节点用来比较两个链表的头谁更小

    比如这两个链表,head1的值明显小于head2,所以把head1的位置放到这个节点中

    然后head1往后继续遍历

    设立一个tempH记录串联好的链表的最后一个节点,两个链表的head相互比较,看看谁的值大tempH就挪到大值的位置,对应的head往后走

    代码写上


    重要的一道题(有深度有高度) 

    链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

    描述

    对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

    给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

    测试样例:

    1->2->2->1
    返回:true

    1.找到中间位置

    和前面一样设置fast和slow指针,fast每次走两步,slow每次走一步,直到fast到末端

    2.翻转--> 从中间节点以后开始翻转

    curNext = cur.next

    cur.next = slow

    slow = cur

    cur = curNext

    3.前->后   后->前

     代码:

    1. public class PalindromeList {
    2. public boolean chkPalindrome(ListNode head) {
    3. if (head == null || head.next == null) {
    4. return true;
    5. }
    6. ListNode fast = head;
    7. ListNode slow = head;
    8. //1.找到中间位置
    9. while (fast != null && fast.next != null) {
    10. fast = fast.next.next;
    11. slow = slow.next;
    12. }
    13. //2.翻转
    14. ListNode cur = slow.next;
    15. while (cur != null) {
    16. ListNode curNext = cur.next;
    17. cur.next = slow;
    18. slow = cur;
    19. cur = curNext;
    20. }
    21. //3.从前到后 从后到前
    22. while (head != slow) {
    23. //顺序不能换,一定得在值相等的情况下才来判断next
    24. if (head.val != slow.val) {
    25. return false;
    26. }
    27. if (head.next == slow) {
    28. return true;
    29. }
    30. head = head.next;
    31. slow = slow.next;
    32. }
    33. return true;
    34. }
    35. }

    链表分割_牛客题霸_牛客网 (nowcoder.com)

    描述

    现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

    假设有这样一段链表

    我给一个x=15,那么小于15的就得放在前面,大于15的排在后面

    那么5和34就得更改位置

    我们可以这么做,设置一个cur遍历整个列表,找到的节点=x的放在右边

    分好类再把他们串起来,最后返回头节点就🆗了

    因为题目还要求不能改变原来的数据顺序,所以在分类的时候我们要用尾插法

    分好类的链表的开头结尾定义四个变量

    框架代码

    当我们插入第一个节点的时候,bs和be都在同一个位置

    所以我们要说明bs = null时,链表正在插入第一个节点

    在插入第二个节点的时候

    bs.next = be;

    be = be.next;

    在插>=x的第一个节点的时候

    as和ae都指向同一个节点,而且刚好cur遍历到这个节点,所以这俩都=cur

    插入后面的节点,as.next = cur; ae = ae.next;(跟前面一样)

    注意在最后要提一嘴be.next = as

    目前的代码是这样,提交之后系统给我们报了一个空指针异常

    因为在执行3,{3,3,3}样例的时候,这三个3>=3所以都被分到右边的链表里面,左边的链表是空的

    bs和be这两个就成了空指针了

    那我们就判断是不是第一个区间为空,空了就返回as

    1. if(bs == null){
    2. //第一个区间没有数据
    3. return as;
    4. }

    提交上去还有这个异常

    这是为什么?我们画个图来看下

    看出问题了吗?

    问题在于最后一个节点next没有置空,导致返回bs给牛客后台的时候,他的后台直接死循环了,导致他误以为你数组越界了

    这种情况属于第一个区间有数据,第二个区间只有一个数据,所以我们要判断第二个区间的as是否为空,空了就给它ae.next = null,空了就无所谓,当预防了

    🆗再提交直接拿下

    完整代码:

    1. public class Partition {
    2. public ListNode partition(ListNode pHead, int x) {
    3. if(pHead == null){
    4. return null;
    5. }
    6. ListNode bs = null;
    7. ListNode be = null;
    8. ListNode as = null;
    9. ListNode ae = null;
    10. ListNode cur = pHead;
    11. while(cur!=null){
    12. if(cur.val < x){
    13. if(bs == null){
    14. bs = cur;
    15. be = cur;
    16. }else{
    17. be.next = cur;
    18. be = be.next;
    19. }
    20. }else{
    21. if(as == null){
    22. as = cur;
    23. ae = cur;
    24. }else{
    25. ae.next = cur;
    26. ae = ae.next;
    27. }
    28. }
    29. cur = cur.next;
    30. }
    31. //串起来
    32. if(bs == null){
    33. //第一个区间没有数据
    34. return as;
    35. }
    36. //第一个区间有数据
    37. be.next = as;
    38. if(as != null){
    39. ae.next = null;
    40. }
    41. return bs;
    42. }
    43. }

    160. 相交链表 - 力扣(LeetCode)

    给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

    图示两个链表在节点 c1 开始相交

    题目数据 保证 整个链式结构中不存在环。

    注意,函数返回结果后,链表必须 保持其原始结构 。

    自定义评测:

    评测系统 的输入如下(你设计的程序 不适用 此输入):

    • intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
    • listA - 第一个链表
    • listB - 第二个链表
    • skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
    • skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数

    评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。

    示例 1:

    输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
    输出:Intersected at '8'
    解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
    从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
    在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
    — 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
    

    示例 2:

    输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
    输出:Intersected at '2'
    解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
    从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。
    在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
    

    示例 3:

    输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
    输出:null
    解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
    由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
    这两个链表不相交,因此返回 null 。
    

     

    提示:

    • listA 中节点数目为 m
    • listB 中节点数目为 n
    • 1 <= m, n <= 3 * 104
    • 1 <= Node.val <= 105
    • 0 <= skipA <= m
    • 0 <= skipB <= n
    • 如果 listA 和 listB 没有交点,intersectVal 为 0
    • 如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB]

    写这道题之前,我们要先明确链表相交的定义

    不是说两条链表里面有一个值一样就叫相交,而是要相交之后后半段链表的值都一样才算

    具体长这样

    上下两个链表各设一个head,遍历,当两个head相遇的时候那个节点就是相遇节点

    当然两个链表前半段不一定等长,像这种

    解决办法就是让最长的先走len(差值)步(len = abs(len1-len2))

    第1步:分别求长度(用两个指针遍历一遍)

    第一段代码:初步

    写完这个代码我们可以保证

    cur1一定指向最长的链表,cur2一定指向最短的链表
    len3一定是一个正数

    但是这段代码存在一个问题:如果len3>0的话,cur1和cur2没办法进入到判断len3<0的分支里面,cur1和cur2在上面的循环之后走到各自的链表的末端,直接为空了,不方便后面第二次遍历

    所以两个循环之后还要强调

    4.相遇

    第2步:最长的走差值步(第二遍遍历)

    1. while(len3 != 0){
    2. cur1 = cur1.next;
    3. len3--;
    4. }

    第3步:同时走 + 第4步:相遇

    1. //3.同时走 --> 4.相遇
    2. while(cur1 != cur2){
    3. cur1 = cur1.next;
    4. cur2 = cur2.next;
    5. }
    6. return cur1;

    整个的代码:

    1. public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    2. int len1 = 0;
    3. int len2 = 0;
    4. ListNode cur1 = headA;
    5. ListNode cur2 = headB;
    6. //求长度
    7. while (cur1 != null) {
    8. len1++;
    9. cur1 = cur1.next;
    10. }
    11. while (cur2 != null) {
    12. len2++;
    13. cur2 = cur2.next;
    14. }
    15. cur1 = headA;
    16. cur2 = headB;
    17. int len3 = len1-len2;
    18. if(len3<0){
    19. //修正一下cur1和cur2的指向
    20. cur1 = headB;
    21. cur2 = headA;
    22. len3 = len2-len1;
    23. }
    24. //cur1一定指向最长的链表,cur2一定指向最短的链表
    25. //len3一定是一个正数
    26. //2.走差值步
    27. while(len3 != 0){
    28. cur1 = cur1.next;
    29. len3--;
    30. }
    31. //3.同时走 --> 4.相遇
    32. while(cur1 != cur2){
    33. cur1 = cur1.next;
    34. cur2 = cur2.next;
    35. }
    36. if(cur2 == null){
    37. return null;
    38. }
    39. return cur1;
    40. }

    细节问题:

    思考一下,当整段代码走到return这里的时候,是不是就代表他们相遇了?

    其实不然,虽然结果是对的,但是这句话本身有漏洞。

    试想一个等长平行链表

    ha和hb走到末端直接为空了,他们根本就没相遇

    那结果为什么是对的?

    因为题目要求

    刚好这俩走到末尾为空,直接被返回了

     🆗我们加上一句

    1. if(cur2 == null){
    2. return null;
    3. }


     141. 环形链表 - 力扣(LeetCode)

    给你一个链表的头节点 head ,判断链表中是否有环。

    如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

    如果链表中存在环 ,则返回 true 。 否则,返回 false 。

    示例 1:

    输入:head = [3,2,0,-4], pos = 1
    输出:true
    解释:链表中有一个环,其尾部连接到第二个节点。
    

    示例 2:

    输入:head = [1,2], pos = 0
    输出:true
    解释:链表中有一个环,其尾部连接到第一个节点。
    

    示例 3:

    输入:head = [1], pos = -1
    输出:false
    解释:链表中没有环。
    

    提示:

    • 链表中节点的数目范围是 [0, 104]
    • -105 <= Node.val <= 105
    • pos 为 -1 或者链表中的一个 有效索引 。

    分析:

    如何判断链表里面有环?

    我们可以在链表里设置两个指针fast和slow,fast跑得快可以先到达环,在环里面fast一定会跟slow相遇,这相当于我们数学的追及问题

    那问题来了,怎么确定fast走几步slow走几步

    一般情况我们让fast走两步,slow走1步,这种情况最快相遇(因为最多一个环的长度内就能fast追上),每次走完就判断一次

    那fast走3步,slow走1步可以吗?

    假设链表有两个节点,这两个节点连成一个环,这种走法就行不通了

    如图,fast反复横跳,总是能刚好跳到slow的对面

    再注意一个点:一定要判断fast != null 和 fast.next != null

    一切准备就绪,直接上代码


    什么?这道题太简单了?

    我们上升一个难度:求一下进环的入口点和fast与slow的相遇点

    142. 环形链表 II - 力扣(LeetCode)

    给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

    如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

    不允许修改 链表。

    示例 1:

    输入:head = [3,2,0,-4], pos = 1
    输出:返回索引为 1 的链表节点
    解释:链表中有一个环,其尾部连接到第二个节点。
    

    示例 2:

    输入:head = [1,2], pos = 0
    输出:返回索引为 0 的链表节点
    解释:链表中有一个环,其尾部连接到第一个节点。
    

    示例 3:

    输入:head = [1], pos = -1
    输出:返回 null
    解释:链表中没有环。

    设起始点到入口点的距离为X,入口点到相遇点的距离为Y,环的长度为C

    情况1:环够大,fast只比slow走多一圈

    那fast走的路程为 X+C+(C-Y)

    slow走的路程为 X+(C-Y)

    fast速度是slow的2倍,那么走的路程也是2倍关系

    列出方程

    X + C + ( C - Y )  = 2 * [ X + ( C - Y ) ]

    求解方程(化简结果)

    X = Y

    情况2:环太小了,fast在里面套圈了,比slow多走了n圈

    那fast走的路程为 X+N*C+(C-Y)

    slow走的路程为 X+(C-Y)

    X + N * C + ( C - Y )  = 2 * [ X + ( C - Y ) ]

    求解方程

    其实无论n多大,fast最终都会跟slow相遇

    简单一点,不妨设N=1,直接X = Y

    既然X=Y了,那我们何不让slow从起始点走,让fast从相遇点走

    让fast速度降到和slow一样,同时同速走就能同时到达入口点

    其实我们只要在上一个代码的基础上加上后半部分的入口点相遇就行了

    整个的代码:

  • 相关阅读:
    TCP/IP之IP地址分类
    亚商投资顾问 早餐FM/0920 苹果涨2.51%,领涨道指
    python中使用多线程批量导入包
    技术面试(二)面试如何准备
    MP157-0-遇见的问题及解决办法
    树选择排序(Tree Selection Sorting)介绍
    mmrotate自定义数据集安装部署训练测试
    【JavaScript高级】05-JavaScript中with、eval语句及严格模式的使用
    【Nginx】负载均衡、动静分离理论篇
    Go 接口-契约介绍
  • 原文地址:https://blog.csdn.net/hellg/article/details/133757167