检测一个较大的单链表是否有环

【阿里巴巴笔试题】

题目描述:

单链表有环指的是单链表中某个结点的next域指向的是链表中它之前的某一个结点,这样在链表的尾部形成一个环形结构。如何判断单链表是否有环?如何找到环的入口点?、

方法一:哈希集合法
思路:可以定义一个空集合,接着从头遍历这个单链表,如果当前结点的next不在集合中就继续往后走,如果在集合中,当前结点的next就是环的入口。

def check(link: SingleLink):
    s = set()  # 构造一个空的set
    p = link.head
    if p is None:  # 空链表直接返回False
        return False
    s.add(p)  # 不空的话把第一个结点加入到s中
    while p.next is not None:  # 如果当前结点的next不是空的话
        if p.next in s:  # 当前结点的下一个结点在集合中
            return p.next.data  # 入口就是当前结点的下一个结点
        s.add(p.next)  # 不在就把当前结点加入到集合中
        p = p.next
    return False  # 如果一直到结尾都没有,那就不是环,返回False

方法二:快慢指针法
我们设置一个慢指针slow和一个快指针fast,慢指针每次向后走一步,快指针每次走两步,因为有环,所以快指针早晚会追上慢指针的。如下图(自己手画的):
在这里插入图片描述
我们可以清楚的看到,对于这个有环的单链表,快指针会走到结尾7之后再沿着环绕回来最终在5结点的位置追上慢结点,此时我们的fast和slow指针都指向这个5结点;
紧接着我们用一个新的变量a指向链表的开头,然后让a和fast同时往后走,每次都走一步,最终会在4结点相遇,这个4结点就是链表的环的入口。这样我们就找到了环形链表的入口了。
这样简单理解很容易,其实这个思路后面是由Floyd算法在做支撑的,Floyd判圈算法也称为龟兔赛跑算法,可以判断链表迭代函数有限状态机是否有环,如果有就可以找出环的起点和大小,时间复杂度是O(n),空间复杂度是O(1)。
扩展链接:Floyd算法
本题代码实现:

# 快慢指针法
def check(link: SingleLink):
    slow = fast = link.head  # 快慢指针都指向 第一个结点
    circle = False  # 假设没有环
    while slow and fast and fast.next is not None:  #
        slow = slow.next   # 慢结点一次走一步
        fast = fast.next.next  # 快结点一次走两步
        if slow and slow == fast:  # 如果快结点等于了慢结点,那么就说明有环
            circle = True
            break  # 直接出循环
    if not circle:
        return False  # 如果没有环直接退出

	# 有环,找到环的入口点
    a = link.head   # a从第一个结点开始
    while a != fast:  # fast此时是相遇的结点
        a = a.next
        fast = fast.next   # a和fast都每次向后移动一步,直到相遇,相遇的地方就是环的入口
    return a.data  # 返回入口点的data

测试
先创建一个有环链表:
链表环的入口点是3


class Node:  # 创建一个结点类,用来生成结点
    def __init__(self, data):
        self.data = data

class SingleLink:  # 创建一个单链表类
    def __init__(self):
        self.head = None
        self.tail = None

    def append(self, x):  # 尾部追加方法
        if self.head is None:
            self.head = self.tail = Node(x)
            return self
        self.tail.next = Node(x)
        self.tail = self.tail.next
        return self

# 构造一个有环链表
link1 = SingleLink()
for i in range(1, 10):
    link1.append(i)
link1.tail.next = link1.head.next.next  # 构造一个环

分别调用两个函数,查看打印结果:

# 快慢指针法
def check(link: SingleLink):
    slow = fast = link.head  # 快慢指针都指向 第一个结点
    circle = False  # 假设没有环
    while slow and fast and fast.next is not None:  #
        slow = slow.next   # 慢结点一次走一步
        fast = fast.next.next  # 快结点一次走两步
        if slow and slow == fast:  # 如果快结点等于了慢结点,那么就说明有环
            circle = True
            break  # 直接出循环
    if not circle:
        return False

    a = link.head   # a从第一个结点开始
    while a != fast:  # fast此时是相遇的结点
        a = a.next
        fast = fast.next   # a和fast都每次向后移动一步,直到相遇,相遇的地方就是环的入口
    return a.data  # 返回入口点的data
    
print(check(link1))  # 3

########################################
# 哈希集合法:
def check(link: SingleLink):
    s = set()  # 构造一个空的set
    p = link.head
    if p is None:  # 空链表直接返回False
        return False
    s.add(p)  # 不空的话把第一个结点加入到s中
    while p.next is not None:  # 如果当前结点的next不是空的话
        if p.next in s:  # 当前结点的下一个结点在集合中
            return p.next.data  # 入口就是当前结点的下一个结点
        s.add(p.next)  # 不在就把当前结点加入到集合中
        p = p.next
    return False  # 如果一直到结尾都没有,那就不是环,返回False

print(check(link1))  # 3

测试结果没问题。

发布了70 篇原创文章 · 获赞 45 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_38727847/article/details/102811795