给定一个链表,求入环的第一个节点

文章讨论了如何使用快慢指针算法检测链表中的环,并找到环的入口点。通过分析快慢指针在环内的移动距离,得出当它们再次相遇时,相遇点到入口点的距离等于起始点到相遇点的距离。文章还处理了环非常小的情况,提供了计算fast指针走过圈数后与slow相遇的方法。

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL,最后有力扣题的链接

根据上面的动图我们可以推出下列结论:

前提:fast和slow相遇了

 1.fast走的路程是多少:

答:X+C+(C-Y)

2.slow走的路程是多少:

答:X+C-Y

3.fast走的路程是slow的两倍:

因为fast一次走两步,slow一次走一步,下面是一个数学问题,把分别对应的路程带入然后化简即可

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

X + X + C + C - Y - Y = X + C + C - Y

X - Y = 0 

X = Y

通过这个结论我们可以得出,起始点的距离到入口点的距离 和 相遇点的距离到入口点的距离是相等的,所以我们以相同的速度,一个从起始点出发一个从相遇点出发,再次相遇,就是入口点!!

但是这里又出现了新的问题,如果环很小我们应该怎么办呢?

这种情况下,slow已经走完很多圈了,但是fast还没有走到入口点的位置,我们又该如何应对,请看下面的解释

1.fast走的路程是多少:

答:X+NC+(C-Y)   //N代表的是圈数

2.slow走的路程是多少:

答:X+C-Y

3.fast走的路程是slow的两倍:

2*(X + C - Y) = X + NC + C - Y

X + X + C + C - Y - Y = X + NC + C - Y

X + C - Y = NC

X = NC - C + Y

X = (N-1)C + Y

如果N为1,那么X = Y

最后我们附上力扣的题的链接:. - 力扣(LeetCode)

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(fast == slow){
                fast = head;
                while(fast != slow){
                    fast = fast.next;
                    slow = slow.next;
                }
                return slow;
            }
        }
        return null;
    }
}

首先得判断是否成环,如果是环那么在找入口点,如果不是环,那么返回null

<think>嗯,我现在需要解决一个链表问题,题目是找出链表节点,如果没就返回null。让我仔细想想该怎么处理这个问题。 首先,我记得链表的情况通常可以用快慢指针来判断,比如Floyd判圈算法。快指针每次走两步,慢指针每次走一步,如果有的话它们会相遇。那如何根据相遇点找到口呢?这可能是我需要进一步思考的地方。 假设链表头到口的距离是A,口到相遇点的距离是B,的剩余部分是C。当快慢指针相遇时,慢指针走了A+B步,而快指针可能已经绕n圈了,所以快指针走了A+B+n*(B+C)步。根据快指针速度是慢指针的两倍,所以有2(A+B) = A+B +n(B+C),简化得到A = n(B+C) - B。这时候如果让一个指针从头开始走,另一个从相遇点走,每次走一步,他们相遇的地方就是口。这可能是因为当n=1时,A = B + C - B = C,所以头指针走A步到口,而相遇点的指针走C步刚好也到口。但这里可能需要更详细的分析。 不过具体的步骤应该是什么呢?首先,确认是否有,快慢指针相遇即有。然后,找到相遇点后,如何找到口。这时需要两个指针,一个从head开始,另一个从相遇点开始,每次各走一步,直到相遇,那个点就是口。这个结论是否正确呢? 比如,假设当快慢指针相遇时,慢指针走了A+B步,快指针走了A+B+n*(B+C)步。根据速度关系,快指针是两倍速,所以2(A+B) = A+B +n*(B+C),即A+B = n*(B+C)。这时候,如果让指针从head出发,走A步到口。而另一个指针从相遇点出发,走A步的话,因为A = n*(B+C) - B,所以移动A步的话,相当于从相遇点移动n*(B+C) - B步。因为B+C是的长度,所以n*(B+C)相当于绕n圈回到原点,再减去B步的话,就相当于从相遇点向回走B步,也就是到达口点。这样两个指针会在口点相遇。所以这个方法是可行的。 那具体实现的话,在Python中应该怎么做呢?首先定义快慢指针,找到相遇点。如果没有相遇,说明无。如果有相遇点,再从头和相遇点同步移动指针,直到相遇即为口。 那代码的大体结构应该是这样的: 1. 初始化快慢指针都指向头节点。 2. 移动快指针两步,慢指针一步,直到它们相遇或者快指针走到空。 3. 如果相遇,则将一个指针重置到head,另一个保持在相遇点,然后同步移动,直到再次相遇的点就是口。 4. 如果无,返回None。 需要注意边界情况,比如头节点为空,或者只有一个节点自成的情况。 举个例子,比如链表是1->2->3->4->2(口是节点2)。按照步骤,快慢指针会在节点4和节点2的某个点相遇?比如,慢指针走1,2,3,4,快指针走2,4,2,4。当慢指针到3的时候,快指针可能在4。然后慢到4,快到2;再慢到2,快也到2?或者可能相遇在节点4? 可能具体的例子需要画图来理解。但无论如何,算法逻辑是正确的。 那现在编写Python代码的话,需要处理指针的移动,以及可能的空指针情况。比如在移动快指针的时候,每次都要检查next和next.next是否存在,否则可能抛出异常。 所以具体代码步骤: - 判断head是否为空,或者head.next是否为空,直接返回None。 - 初始化slow和fast为head。 - 进,快指针每次两步,慢指针每次一步。 - 当快指针或fast.next为None时,说明无,返回None。 - 当slow等于fast时,找到相遇点。 - 然后,将其中一个指针(比如ptr1)指向head,另一个(ptr2)保持在相遇点。 - ptr1和ptr2每次移动一步,直到相遇,该节点就是口。 例如: def detectCycle(head): if not head or not head.next: return None slow = head fast = head has_cycle = False # 找相遇点 while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: has_cycle = True break if not has_cycle: return None # 找口点 ptr1 = head ptr2 = slow while ptr1 != ptr2: ptr1 = ptr1.next ptr2 = ptr2.next return ptr1 测试这个代码是否正确。比如,当链表是1->2->3->4->2,相遇点可能在某个节点,然后ptr1和ptr2移动后会在2处相遇,即口。 可能还需要考虑其他测试用例,比如头节点就是口的情况,或者很大,或者小等。 总的来说,这个方法的时间复杂度是O(n),空间复杂度是O(1),符合要。</think>要找到链表节点,可以通过以下步骤实现: --- ### 方法思路(Floyd判圈算法) 1. **判断是否有** 使用快慢指针,快指针每次移动两步,慢指针每次移动一步。若相遇,则有;若快指针遇到空节点,则无。 2. **找到口** 当快慢指针相遇后,将一个指针重置到链表头,另一个保持在相遇点。两指针同步移动(每次一步),再次相遇的节点即为口。 --- ### 数学推导 设链头到口的距离为 $A$,口到相遇点的距离为 $B$,的剩余部分为 $C$。 - 快指针路程:$A + B + n(B + C)$(绕 $n$ 圈) - 慢指针路程:$A + B$ - 快指针速度是慢指针的两倍,故 $2(A + B) = A + B + n(B + C)$ 化简得 $A = n(B + C) - B$ 当两指针从链表头和相遇点同步移动时,必在口相遇。 --- ### Python实现代码 ```python class ListNode: def __init__(self, x): self.val = x self.next = None def detectCycle(head: ListNode) -> ListNode: if not head or not head.next: return None slow = head fast = head has_cycle = False # 步骤1:判断是否有,并找到相遇点 while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: has_cycle = True break if not has_cycle: return None # 步骤2:找到口 ptr1 = head ptr2 = slow while ptr1 != ptr2: ptr1 = ptr1.next ptr2 = ptr2.next return ptr1 ``` --- ### 代码解析 1. **边界处理** 若链表为空或仅有一个节点,直接返回 `None`。 2. **快慢指针找** - 快指针每次走两步,慢指针走一步。 - 若相遇,标记存在;若快指针遇到空节点,说明无。 3. **确定口** - 将 `ptr1` 指向头节点,`ptr2` 保持在相遇点。 - 两指针同步移动,相遇点即为口。 --- ### 复杂度 - **时间复杂度**:$O(n)$,遍历链表的时间。 - **空间复杂度**:$O(1)$,仅使用固定数量的指针。 此方法高效且无需额外空间,是解决此类问题的标准算法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值