数据结构之链表的经典笔试题

news2024/11/27 18:51:25

 找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程(ಥ_ಥ)-CSDN博客

所属专栏:数据结构(Java版)

目录

203. 移除链表元素

206. 反转链表

876. 链表的中间节点

面试题 02.02. 返回倒数第k个节点

快慢指针的原理分析 

21. 合并两个有序链表

牛客网——链表的回文

牛客网——链表分割

160. 相交链表

 141. 环形链表

142. 环形链表 II


单链表的刷题,点我

下面的这些面试题有一部分在上面这篇博文中,有兴趣可以看一看。

下列题目中出现的这个,是出题者想告诉我们这个链表的节点是由什么组成的。 

203. 移除链表元素

给你一个链表的头节点 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

思路:就是遍历链表,找到所有值为val的节点,并删除即可。 

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 删除链表中所有值为节点
        // 首先判断链表是否为空
        if (head == null ) {
            // 链表为空,肯定head为空
            return head;
        }
        ListNode cur = head;
        while (cur.next != null) {
            // 找到值为val的前一个节点
            if (cur.next.val == val) {
                cur.next = cur.next.next;
            }else {
                // 没找到就继续往后找
                cur = cur.next;
            }
        }
        // 判断头节点是否为val
        if (head.val == val) {
            head = head.next;
        }
        return head;
    }
}

206. 反转链表

给你单链表的头节点 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

思路:遍历原链表,将其中的所有节点全部头插到原链表中。

class Solution {
    public ListNode reverseList(ListNode head) {
        // 当链表为空,或者只有一个节点时,就直接返回原链表
        if (head == null || head.next == null) {
            return head;
        }
        // 开始头插
        ListNode cur = head.next;
        // 这个要变为null,否则就会成环。
        // 因为这个节点是最后一个节点,而最后一个节点的next值不为null,就会造成错误
        head.next = null; 
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = head; 
            head = cur;
            cur = curNext;
        }
        return head;
    }
}

876. 链表的中间节点

给你单链表的头结点 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

思路一:通过遍历链表得出链表的长度,然后让cur从头节点开始走 长度的一半 的步数,那么这个节点的位置就是中间节点。

思路二:通过快慢指针的方式来解题。快指针fast,一次走两步,慢指针slow,一次走一步。当快指针走到链表的尾节点处时,慢指针就是在当前链表的中间节点。

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        // 只要两者有一种情况不满足,就说明走到了尾节点,可以停止遍历了。
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow;
    }
}

面试题 02.02. 返回倒数第k个节点

 实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

注意:本题相对原题稍作改动

示例:

输入: 1->2->3->4->5 和 k = 2
输出: 4

说明:

给定的 k 保证是有效的。

思路一:遍历得出链表的长度,然后用长度减去k,得出的结果就是cur要走的步数。

思路二:通过快慢指针的方式来解题。快指针先走k-1步,然后快指针和慢指针一起走,直至快指针到达尾节点,这时慢指针的位置就是倒数第k个节点的位置。

class Solution {
    public int kthToLast(ListNode head, int k) {
        ListNode fast = head;
        ListNode slow = head;
        while ((k-1) > 0) {
            fast = fast.next;
            k--;
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow.val;
    }
}

快慢指针的原理分析 

通过这两题,我们来分析快慢指针的原理。

链表的中间节点 —— 假设有两个人在同一时间赛跑,fast是slow的速度的二倍,当fast到达终点时,这个slow就会在这个总路程的一半处。因此就可以得出中间节点的位置。

返回倒数第k个节点 —— fast先走k-1步,那么fast与slow就相差k-1步,当两者一起走,并且fast走到了终点时,这时fast与slow还是相差k-1步,那么这个slow就是倒数第k个节点(因为fast的位置是倒数第一个节点)。

21. 合并两个有序链表

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

示例 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 均按 非递减顺序 排列

思路:创建一个新的带头链表。通过遍历两个链表,比较两者的大小,将 小的val值 对应的节点尾插到这个带头链表中,直至有一方节点结束,再把另一方的节点直接尾插到新链表即可。

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // 当list1为空或者list1与list2都为空时,直接返回list2
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }
        // 创建一个新的头节点
        ListNode head = new ListNode(-1);
        ListNode cur = head;
        ListNode cur1 = list1;
        ListNode cur2 = list2;
        while (cur1 != null && cur2 != null) {
            if (cur1.val > cur2.val) {
                // 尾插cur2
                cur.next = cur2;
                cur2 = cur2.next;
            }else {
                // 尾插cur1
                cur.next = cur1;
                cur1 = cur1.next;
            }
            // 因为这一行代码无论走哪里,都会进行,因此放到外边来
            cur = cur.next;
        }
        // 还有一方还剩节点
        if (cur1 != null) {
            // 尾插cur1就行了,因为原来就是链表,因此不用循环
            cur.next = cur1;
        }
        if (cur2 != null) {
            // 尾插cur2就行了,因为原来就是链表,因此不用循环
            cur.next = cur2;
        }
        return head.next;
    }
}

牛客网——链表的回文

描述

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

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

测试样例:

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

思路:虽然描述很简洁,但是难度却是比较大的一题。按照我们以往写回文的思路,就是定义一个left 和 right 指针指向两头,遍历比较。遇到不相等的就直接返回false,遍历完就返回true。但很可惜这题是单链表不能够反向遍历。既然不能直接反向遍历,那么我们就把这个后面的链表反转一下,变成可以反向遍历的情况。那问题又来了:从哪里开始反转呢?我们想一下,普通回文遍历的结束条件是left < right ,也就是两者相遇或者说是到了中间位置就可以停止了。那么我们这里的反转也就可以从中间位置开始。反转完,之后就可比较了。因此,最终要做的事情就是:

1. 找到链表的中间节点;

2. 从中间位置开始反转;

3. 从两头开始遍历比较。

下面是反转的具体分析过程:

public class PalindromeList {
    public boolean chkPalindrome(ListNode A) {
        // 当链表为空或者只有一个节点时,必回文
        if (A == null || A.next == null) {
            return true;
        }
        // 找到中间节点
        ListNode fast = A;
        ListNode slow = A;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        ListNode cur = slow.next;
        // 从中间节点往后反转
        // 当slow走到尾节点时,就可以不必再继续了
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }
        // 开始遍历回文
        // 这里不要用fast,因为fast可能不在尾节点
        while (A != slow) {
            if (A.val != slow.val) {
                return false;
            }
            // 当链表长度为偶数时,会出现一种情况(后面细说)
            if (A.next == slow) {
                return true;
            }
            A = A.next;
            slow = slow.next; 
        }
        return true;
    }
}

反转也不能写成下面这样:

    while (slow.next != null) {
            ListNode cur = slow.next;
            ListNode curNext = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curNext;
        }

上面的代码会陷入死循环。因为在最后修改了 slow 的位置,变成了 cur,所以 slow.next 是指向的位置是原来的 slow 。因此最终就陷入了死循环。如下图:

最后,我们会发现其实改来改去最终都在兜圈子。 

综上本题来看,难度确实是挺大的,要注意的细节也是挺多的,无愧于 字节跳动 的秋招笔试题。 

牛客网——链表分割

描述

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

思路:既然要分割链表,那我们直接按照原来的链表顺序遍历,把小于 x 的节点放到小链表中,其余节点放到大链表中。 

我们就会不假思索的写出下面的代码:

public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        // 这题比较虾仁猪心:思路简单,但细节很多
        // 如果链表为空或者链表只有一个节点,就直接返回该节点
        if (pHead == null || pHead.next == null) {
            return pHead;
        }
        // 为了更为方便,我们定义头节点和尾节点
        ListNode beforeStart = null;
        ListNode beforeEnd = null;
        ListNode afterStart = null;
        ListNode afterEnd = null;
        // 遍历链表,小于放一边,大于放一边,然后小的串大的
        while (pHead != null) {
            if (pHead.val < x) {
                // 尾插到小链表中
                if (beforeStart == null) {
                    beforeStart = pHead;
                    beforeEnd = pHead;
                } else {
                    beforeEnd.next = pHead;
                    beforeEnd = beforeEnd.next;
                }
            } else {
                // 尾插到大链表中
                if (afterStart == null) {
                    afterStart = pHead;
                    afterEnd = pHead;
                } else {
                    afterEnd.next = pHead;
                    afterEnd = afterEnd.next;
                }
            }
            pHead = pHead.next;
        }
        beforeEnd.next = afterStart;
        return beforeStart;
    }
}

但上面的代码在测试的时候,会抛出空指针异常。其实就是当小链表为空(链表中所有的值均大于 x)时,这个 beforeStart 为空,那么去访问其 next 值时,就会发生空指针异常。因此,当小链表为空时,直接返回大链表,当大链表为空时,直接返回小链表即可。但很遗憾,又出现了死循环的问题:如下图所示:

因此,我们还得把 afterEnd 的 next 置为null。 通过一系列的纠正,最终的代码如下:

public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        // 这题比较虾仁猪心:思路简单,但细节很多
        // 如果链表为空或者链表只有一个节点,就直接返回该节点
        if (pHead == null || pHead.next == null) {
            return pHead;
        }
        // 为了更为方便,我们定义头节点和尾节点
        ListNode beforeStart = null;
        ListNode beforeEnd = null;
        ListNode afterStart = null;
        ListNode afterEnd = null;
        // 遍历链表,小于放一边,大于放一边,然后小的串大的
        while (pHead != null) {
            if (pHead.val < x) {
                // 尾插到小链表中
                if (beforeStart == null) {
                    beforeStart = pHead;
                    beforeEnd = pHead;
                } else {
                    beforeEnd.next = pHead;
                    beforeEnd = beforeEnd.next;
                }
            } else {
                // 尾插到大链表中
                if (afterStart == null) {
                    afterStart = pHead;
                    afterEnd = pHead;
                } else {
                    afterEnd.next = pHead;
                    afterEnd = afterEnd.next;
                }
            }
            pHead = pHead.next;
        }
        
        // 把小链表和大链表串起来
        if (beforeEnd == null) {
            // 如果小链表为空,那么就直接返回大链表
            // 那么大链表就是按照原来链表的顺序,即无需将最后节点的next置为null
            return afterStart;
        }
        if (afterStart == null) {
            return beforeStart;
        }
        beforeEnd.next = afterStart;
        afterEnd.next = null;
        return beforeStart;
    }
}

这题虽然思路简单,但要注意的细节很多,稍一疏忽,就会犯错。简直就是:虾仁猪心 。

160. 相交链表

给你两个单链表的头节点 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]

思路: 两个链表相交,有一个特点就是:相交之后的链表长度一定是相同的。假设前面不相交的长度是想等的,定义一个 cur1 指向一个链表,定义一个 cur2 指向另一个链表,去遍历两个链表,当两者不相等时,就一直遍历,直至相等为止。但遗憾的是两者长度会出现不一样的情况,这里依然是使用快慢指针的方法。当长度不相等时,就让 长度长的链表 先走 两者的差值步数,然后再一起走,那么最终的结果一定是两者一起走到相等或者两者走到都为 null。最终的步骤:

1、先算出两者的长度,求其长度差。

2、再让长度长的链表走长度差步

3、接着两者一起走,判断是否相等

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 先算出各自长度,让长的链表走两者长度的差值步,再一起走,如果相遇则其相交。
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        int len1 = 0;
        int len2 = 0;
        while (cur1 != null) {
            len1++;
            cur1 = cur1.next;
        }
        while (cur2 != null) {
            len2++;
            cur2 = cur2.next;
        }
        if (len1 > len2) {
            // 让headA走差值步
            cur1 = headA;
            cur2 = headB;
            int len = len1 - len2;
            while (len-- > 0) {
                cur1 = cur1.next;
            }
            // 开始比较
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            // 走到这里,有两种情况:1、cur1与cur2 都为空了,跳出了循环
            // 2、两者确实是相遇了。 我们只需判断一下即可
            if (cur1 == null) {
                return null;
            }
            // 其实这里不判断,也可以运行通过,因为题目要求不相遇就返回null,
            // 而比较到null时,cur1也是null,所以返回cur1也是对的(碰巧)。
            return cur1;
        }else {
            // 让headB走差值步
            cur1 = headA;
            cur2 = headB;
            int len = len2 - len1;
            while (len-- > 0) {
                cur2 = cur2.next;
            }
            // 开始比较
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            // 走到这里,有两种情况:1、cur1与cur2 都为空了,跳出了循环
            // 2、两者确实是相遇了。 我们只需判断一下即可
            if (cur1 == null) {
                return null;
            }
            // 其实这里不判断,也可以运行通过,因为题目要求不相遇就返回null,
            // 而比较到null时,cur1也是null,所以返回cur1也是对的(碰巧)。
            return cur1;
        }
    }
}

上面的代码在后面一起走的部分中大部分代码是一致的,因此就可以简化为下面这样:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 先算出各自长度,让长的链表走两者长度的差值步,再一起走,如果相遇则其相交。
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        int len1 = 0;
        int len2 = 0;
        while (cur1 != null) {
            len1++;
            cur1 = cur1.next;
        }
        while (cur2 != null) {
            len2++;
            cur2 = cur2.next;
        }
        // 假设headA为长链表
        int len = len1-len2;
        cur1 = headA;
        cur2 = headB;
        if (len < 0) {
            // 说明headB是长链表
            len = - len;
            cur1 = headB;
            cur2 = headA;
        }
        // 走到这里,长链表一定已经确定了
        while (len-- > 0) {
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        if (cur1 == null) {
            return null;
        }
        return cur1;
    }
}

 141. 环形链表

给你一个链表的头节点 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 或者链表中的一个 有效索引 。

思路:快慢指针。当一个链表没有环,那么肯定是快指针先走到终点,如果一个链表有环,则快指针会先入环,接着慢指针再入环,最终快慢指针会相遇(一定是快指针走两步,慢指针走一步,这样快指针才不会包含慢指针)。如果快指针走三步,就有可能出现慢指针一直在快指针中间的情况。

public class Solution {
    public boolean hasCycle(ListNode head) {
        // 如果链表为空或者链表的第一个节点的next为空,则说明没有成环
        if (head == null || head.next == null) {
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        // 快指针如果遇到结尾的情况就可以停止了
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            // 一定要走完才能判断
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
}

其实,我们仔细观察会发现:在快指针还没开始走的时候,while循环的判断条件和前面的if判断条件是一样的,都是判断链表是否为空或者是链表的第一个节点的 next 值是是否为空。

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        // 快指针如果遇到结尾的情况就可以停止了
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            // 一定要走完才能判断
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
}

142. 环形链表 II

给定一个链表的头节点  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
解释:链表中没有环。

提示:

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

思路: 这个题目的意思就是让我们找到链表的第一个成环的节点。如下图所示:

上面那种情况只是我们认为的理想情况:慢指针入环时,快指针刚好只走完一圈,但是实际走完几圈我们是不确定的,因此可以设 n 为快指针走的圈数,带入求解的结果就是:x = n*m - y = (n-1)*m + (m-y),最终结果还是我们前面的结论。因为 (n-1)只是圈数,最终还是会停在这个相遇点。

这里最重要的是抓住:快指针走的路程是慢指针的两倍。 

1、找到相遇点

2、慢指针从头走,快指针从相遇点走,都以相同的速度。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        // 找到相遇点
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            // 相遇就停下来
            if (slow == fast) {
                break;
            }
        }
        // 链表里没有环的情况有几种:
        if (fast != slow || head == null || head.next == null) {
            // 说明没有环
            return null;
        }
        // slow从链表头开始走,fast从相遇点开始走
        slow = head;
        while (slow != fast) {
            fast = fast.next;
            slow =slow.next;
        }
        return slow;
    }
}

因为这里在相遇之后,还要执行代码,就必须要判断链表是否为空和只有一个节点无环的情况。 

好啦!本期 数据结构之链表的经典笔试题 的学习之旅就到此结束啦!我们下一期再一起学习吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1816228.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

零基础非科班也能掌握的C语言知识22 预处理详解(完结)

预处理详解 1.预处理符号2.#define 定义常量3.#define 定义宏4.带有副作用的宏参数5.宏替换的规则6.宏函数的对比6.1 例子6.1 .16.1.26.1.3 7.命名约定8.undefin9.命令行定义(博主没办法演示)10.条件编译11.头文件的包含11.1本地文件11.2库文件的包含11.3 嵌套文件的包含 12.其…

软件安全测评有哪些测试流程?第三方检测机构进行安全测评的好处

在今天的高科技时代&#xff0c;软件产品已经成为人们生活和工作的重要组成部分。然而&#xff0c;与其普及和深入应用的&#xff0c;软件安全问题也日益凸显。 为了保障软件产品在使用过程中的安全性&#xff0c;进行安全测评是必不可少的。安全测评可以全面评估软件系统的安…

GPT-4o多模态大模型的架构设计

GPT-4o&#xff1a;大模型风向&#xff0c;OpenAI大更新 OpenAI震撼发布两大更新&#xff01;桌面版APP与全新UI的ChatGPT上线&#xff0c;简化用户操作&#xff0c;体验更自然。同时&#xff0c;全能模型GPT-4o惊艳亮相&#xff0c;跨模态即时响应&#xff0c;性能卓越且性价比…

Java集合自测题

文章目录 一、说说 List , Set , Map 三者的区别&#xff1f;二、List , Set , Map 在 Java 中分别由哪些对应的实现类&#xff1f;底层的数据结构&#xff1f;三、有哪些集合是线程不安全的&#xff1f;怎么解决呢&#xff1f;四、HashMap 查询&#xff0c;删除的时间复杂度五…

k8s中的pod域名解析失败定位案例

问题描述 我在k8s中启动了一个Host网络模式的pod&#xff0c;这个pod的域名解析失败了。 定位步骤 敲kubectl exec -it [pod_name] -- bash进入pod后台&#xff0c;查看/etc/resolv.conf&#xff0c;发现nameserver配的有问题。这里我预期的nameserver应该使用宿主机的&…

【Linux】线程(一)

谈论之前需要先谈论一些线程的背景知识 其中就有进程地址空间&#xff0c;又是这个让我们又爱又恨的东西 目录 背景知识&#xff1a;地址空间&#xff1a; 背景知识&#xff1a; 地址空间&#xff1a; 说在前边&#xff0c;OS通常分为4个核心模块&#xff1a;执行流管理&…

【qt】绘图

绘图 一.画家二.绘图事件三.坐标体系四.画笔1.setColor2.setWidth3.setStyle4.setCapStyle5.setJoinStyle6.给画家配置笔 五.画刷1.setColor2.setStyle3.给画家设置刷子 六.用到的类汇总1.QRect 矩形2.QPoint 点3.QImage 图片4.QPixmap 图片5.QLine 线6.QPainterPath 路径 七.开…

如何关闭端口被占用的进程

如何关闭端口被占用的进程 操作步骤一、打开命令提示符&#xff08;Command Prompt&#xff09;二、查看占用端口的进程三、kill杀死占用端口的进程 操作步骤 一、打开命令提示符&#xff08;Command Prompt&#xff09; 使用 win R 打开命令行模式 然后在命令行窗口输入下…

【LLM Agent 长文本】Chain-of-Agents与Qwen-Agent引领智能体长文本处理革命

前言 大模型在处理长文本上下文任务时主要存在以下两个问题&#xff1a; 输入长度减少&#xff1a;RAG的方法可以减少输入长度&#xff0c;但这可能导致所需信息的部分丢失&#xff0c;影响任务解决性能。扩展LLMs的上下文长度&#xff1a;通过微调的方式来扩展LLMs的上下文窗…

基于DenseNet网络实现Cifar-10数据集分类

目录 1.作者介绍2.Cifar-10数据集介绍3.Densenet网络模型3.1网络背景3.2网络结构3.2.1Dense Block3.2.2Bottleneck层3.2.3Transition层3.2.4压缩 4.代码实现4.1数据加载4.2建立 DenseNet 网络模型4.3模型训练4.4训练代码4.5测试代码 参考链接 1.作者介绍 吴思雨&#xff0c;女…

搭建自己的多平台镜像站

# 1. 拉取代码 $ git clone https://github.com/wzshiming/crproxy.git $ cd crproxy/examples/default# 2. 修改网关域名 使用vim编辑start.sh文件&#xff0c;将第五行的gateway变量值修改为你自己设定的域名。 原&#xff1a;gatewaycr.zsm.io 修改为&#xff1a;gatewayXS…

go语言 | 快速生成数据库表的 model 和 queryset

就是生成 model 目录的 xxx.go 和 xxx_gen.go 文件 使用的工具&#xff1a; 快速生成 model&#xff1a;gentool&#xff1a;https://github.com/go-gorm/gen/tree/master/tools/gentool 根据 model 生成 queryset&#xff1a;go-queryset&#xff1a;https://github.com/jirfa…

layuimini框架实现点击菜单栏回到起始页

在layui页面中&#xff0c;如果使用了 window.location.href""进行了页面跳转&#xff0c;再点击菜单栏是不会显示起始页&#xff0c;而是跳转后的页面&#xff0c; 解决&#xff1a; 在miniTab.js文件中找到&#xff1a;listen方法 将其中修改为&#xff1a; if …

全球首创4090推理!昆仑万维开源Skywork-MoE模型

昆仑万维近期宣布开源了其2千亿参数规模的稀疏大模型Skywork-MoE。这个模型是基于他们之前开源的Skywork-13B模型中间checkpoint扩展而来的&#xff0c;并且宣称是首个完整应用MoE Upcycling技术的开源千亿MoE大模型。此外&#xff0c;它也是首个支持使用单台RTX 4090服务器&am…

MyBatisPlus代码生成器(交互式)快速指南

引言 本片文章是对代码生成器(交互)快速配置使用流程&#xff0c;更多配置方法可查看官方文档&#xff1a; 代码生成器配置官网 如有疑问欢迎评论区交流&#xff01; 文章目录 引言演示效果图引入相关依赖创建代码生成器对象引入Freemarker模板引擎依赖支持的模板引擎 MyBat…

Day 20:2806. 取整购买后的账户余额

Leetcode 2806. 取整购买后的账户余额 一开始&#xff0c;你的银行账户里有 100 块钱。 给你一个整数purchaseAmount &#xff0c;它表示你在一次购买中愿意支出的金额。 在一个商店里&#xff0c;你进行一次购买&#xff0c;实际支出的金额会向 最近 的 10 的 倍数 取整。换句…

【QT5】<总览二> QT信号槽、对象树及常用函数

文章目录 前言 一、QT信号与槽 1. 信号槽连接模型 2. 信号槽介绍 3. 自定义信号槽 二、QT的对象树 三、添加资源文件 四、样式表的使用 五、QSS文件的使用 六、常用函数与宏 前言 承接【QT5】&#xff1c;总览一&#xff1e; QT环境搭建、快捷键及编程规范。若存在版…

Python使用tkinter库设置背景图片、label显示位置和label设置显示图片

tkinter 设置背景图片 label显示位置 label设置显示图片 from tkinter import * import tkinter as tk from PIL import ImageTk from PIL import Imagedef get_img(filename, width, height):im Image.open(filename).resize((width, height))im ImageTk.PhotoImage(im)…

网络仿真方法综述

目录 1. 引言 2.仿真器介绍 2.1 NS-2 2.2 NS-3 2.3 OPNET 2.4 GNS3 3.仿真对比 4.结论 参考文献 1. 引言 网络仿真是指使用计算机模拟网络系统的行为和性能的过程。在网络仿真中&#xff0c;可以建立一个虚拟的网络环境&#xff0c;并通过模拟各种网络设备、协议和应用程…

this关键字,构造函数(构造器)

文章目录 thisthis是什么应用场景 构造器注意事项代码演示 this this是什么 this就是一个变量&#xff0c;可以在方法中&#xff0c;拿到当前对象 应用场景 解决变量名称 冲突问题 构造器 注意事项 必须和类名相同没有返回值只要参数不同&#xff08;个数不同&#xff0…