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

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

个人主页:我要学编程(ಥ_ಥ)-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;
    }
}

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

想让AI 驱动 UI 测试?墙裂推荐这个自动化工具!

文章概述 本文介绍了什么是视觉测试&#xff0c;功能测试对于视觉测试来说的局限性&#xff0c;视觉测试的重要意义及视觉测试结合python/java两种脚本的案例。 现如今公司不断部署新版本&#xff0c;有些甚至每天都会发布。这种持续部署意味着定期更新或现有代码行正在更改&a…

RabbitMQ概述

RabbitMQ RabbitMQ概述 RabbitMQ是一个开源的消息代理&#xff08;message broker&#xff09;系统&#xff0c;最初由Rabbit Technologies Ltd开发&#xff0c;并在开源社区的支持下不断发展和完善。它提供了强大的消息传递机制&#xff0c;被广泛应用于构建分布式系统和应用…

2003远程桌面端口修改,Windows Server 2003远程桌面端口修改的专业操作指南

在网络安全日益受到重视的今天&#xff0c;修改Windows Server 2003远程桌面的默认端口已成为提高服务器安全性的常规操作。默认情况下&#xff0c;远程桌面使用的端口为3389&#xff0c;这一广为人知的端口号常常成为黑客攻击的目标。因此&#xff0c;通过修改远程桌面端口&am…

JVM的几种常见垃圾回收算法

引言&#xff1a; Java Virtual Machine&#xff08;JVM&#xff09;作为Java程序运行的核心&#xff0c;其垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制在内存管理中起着至关重要的作用。垃圾回收算法是JVM性能优化的重要方面。本文将详细介绍几种常见的垃圾回…

Spring Cloud Stream整合RocketMQ

Spring Cloud Stream整合RocketMQ 这里书接上回&#xff0c;默认你已经搭建好了RocketMQ主从异步集群&#xff0c;前面文章已经介绍过搭建方法。 1、Spring Cloud Stream介绍 Spring Cloud Stream是一个框架&#xff0c;用于构建与共享消息系统连接的高度可扩展的事件驱动微服…

11. 利用MS为Lammps ReaxFF建模(PE/聚乙烯)基础-2

来源&#xff1a; “码农不会写诗”公众号 链接&#xff1a;利用MS为Lammps ReaxFF建模(PE/聚乙烯)基础-2 文章目录 01 msi2lmp工具简介1. 编译生成msi2lmp可执行文件2. 使用方式 02 赋予模型CVFF力场03 导出car/mdf文件04 生成data文件05 data文件进一步处理 文接上篇 上篇利用…

大模型高级 RAG 检索策略之流程与模块化

我们介绍了很多关于高级 RAG&#xff08;Retrieval Augmented Generation&#xff09;的检索策略&#xff0c;每一种策略就像是机器中的零部件&#xff0c;我们可以通过对这些零部件进行不同的组合&#xff0c;来实现不同的 RAG 功能&#xff0c;从而满足不同的需求。 今天我们…

Android RTSP/RTMP多路播放时动态切换输出View类型(SurfaceView和TextureView 动态切换)

SurfaceView和TextureView的区别和优缺点等, 相关的资料很多. 从Android低延时播放器实现角度来看, 总结了下主要区别有: 1. MediaCodec输出到SurfaceView延时一般比到TextureView更低. 2. MediaCodec用SurfaceView比TextureView占用的资源一般更少些(CPU和内存都小一些, 不过还…

算法专题总结链接地址

刷力扣的时候会遇到一些总结类型的题解&#xff0c;在此记录&#xff0c;方便自己以后找 前缀和 前缀和https://leetcode.cn/problems/unique-substrings-in-wraparound-string/solutions/432752/xi-fa-dai-ni-xue-suan-fa-yi-ci-gao-ding-qian-zhui-/ 单调栈 单调栈https:…

论文解读——《I2EDL: Interactive Instruction Error Detection and Localization》

一、研究背景 视觉与语言导航&#xff08;VLN&#xff09;是一个AI领域的研究任务&#xff0c;旨在开发能够按照自然语言指令在三维空间中导航到指定位置的智能体。这项任务与人类的日常活动——如按照口头指示到达某个地点——十分相似&#xff0c;对于推动人机交互的自然性和…

k8s学习--kubernetes服务自动伸缩之水平伸缩(pod副本伸缩)HPA详细解释与案例应用

文章目录 前言HPA简介简单理解详细解释HPA 的工作原理监控系统负载模式HPA 的优势使用 HPA 的注意事项应用类型 应用环境1.metircs-server部署2.HPA演示示例&#xff08;1&#xff09;部署一个服务&#xff08;2&#xff09;创建HPA对象&#xff08;3&#xff09;执行压测 前言…

汇聚荣科技有限公司实力强吗?

汇聚荣科技有限公司实力强吗?在当今快速发展的科技行业中&#xff0c;公司的实力往往决定了其市场竞争力和发展前景。对于汇聚荣科技有限公司而言&#xff0c;其是否具备强大的实力&#xff0c;不仅关系到自身的发展&#xff0c;也影响着投资者和合作伙伴的选择。因此&#xf…

集成算法实验(Bagging策略)

Bagging模型(随机森林) Bagging&#xff1a;训练多个分类器取平均 f ( x ) 1 / M ∑ m 1 M f m ( x ) f(x)1/M\sum^M_{m1}{f_m(x)} f(x)1/M∑m1M​fm​(x) 全称&#xff1a; bootstrap aggregation&#xff08;说白了就是并行训练一堆分类器&#xff09; 最典型的代表就是随…

[ue5]建模场景学习笔记(6)——必修内容可交互的地形,交互沙(4)

1.需求分析&#xff1a; 现在我们已经有了可以在世界内近于无限的跑动痕迹&#xff0c;现在需要对痕迹进行细化&#xff0c;包括例如当人物跳起时便不再绘制痕迹&#xff0c;以及痕迹应该存在深浅&#xff0c;应该由两只脚分别绘制&#xff0c;同时也应该对地面材质进行进一步处…

国内核心期刊基本情况

对于广大师生来说&#xff0c;发表核心期刊论文是当前阶段绕不开的任务&#xff0c;有的高校晋升副高需要发表核心论文5篇以上&#xff0c;有的学校硕博研究生毕业条件必须是一作发核心。很多人对核心的理解仅停留在“北核、南核”&#xff0c;其他的一概不知。但是我国的核心期…

CG-85C 振弦式土压力计厂家 结构物内部土压力变化量如何测量?

产品概述 振弦式土压力计由背板、感应板、信号传输电缆、振弦及激振电磁线圈等组成&#xff0c;是了解被测结构物内部土压力变化量、并可同步测量埋设点温度的监测设备。 功能特点 ◆精度高&#xff0c;能够提供准确的测量结果。 ◆稳定性好&#xff0c;不易受到外界因素的…

端点物联开发教程之(一)什么是端点物联

目录 一、手机端演示 二、开发套件 三、嵌入式端 四、平台端 五、手机端 本项目的交流QQ群:701889554 物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html 物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html 物…

centos7 安装 mysql5.7 LTS

centos7 安装 mysql5.7 LTS 参考&#xff1a; https://blog.csdn.net/EB_NUM/article/details/105425622 可以在运行安装程序之前导入密钥&#xff1a; sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022第一步、下载MySQL 安装包&#xff1a; sudo wget h…

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

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

vs2015+win10编译LAStools

文章目录 下载LasTool安装包编译laslib测试 下载LasTool安装包 不要再GitHub上下载&#xff0c;在官网下载&#xff1a;link 编译laslib 将压缩包解压到对应路径下&#xff0c;注意路径下不要有空格和汉字。用vs打开目录下的 “lastools.dsw” 文件 下面注意几点&#xff1a…