Specimen 06 ยท O(n) time, O(1) space

Fast & Slow Pointers

Two pointers walk the same linked list โ€” one moves a node at a time, the other two nodes at a time. If the list loops, fast laps slow and they land on the same node. If it doesn't loop, fast simply runs off the end.

โ† Back to the Field Guide
06-namuna ยท O(n) vaqt, O(1) xotira

Fast & Slow Pointers

Ikkita pointer bitta linked list bo'ylab yuradi โ€” biri bir vaqtda bitta node, ikkinchisi bir vaqtda ikkita node bosib o'tadi. Agar list loop hosil qilsa, fast pointer slow'ni aylanib o'tadi va ular bir xil node'da uchrashadi. Loop bo'lmasa, fast list oxiriga yetib to'xtaydi.

โ† Qo'llanmaga qaytish

Intuition

Watch the loop below: the solid ring is the tortoise, the dashed ring is the hare, moving twice as fast. On a straight path the hare would simply vanish off the end โ€” but inside a loop, the gap between them shrinks every step until they land on the same node.

Tushuncha (Intuition)

Quyidagi loop'ga qarang: to'liq halqa โ€” toshbaqa, shtrixli halqa esa undan ikki baravar tez harakatlanadigan quyon. To'g'ri yo'lda quyon oxirigacha yetib g'oyib bo'lar edi โ€” lekin loop ichida ular orasidagi masofa har qadamda qisqarib, oxir-oqibat bir xil node'da uchrashadi.

How It Works

  1. Point two pointers at the head of the list to start: slow = head, fast = head. Both begin on the same node โ€” that's fine, since the comparison that matters happens after they've moved.
  2. Check the loop guard before touching fast at all: while fast is not None and fast.next is not None. Both parts matter โ€” advancing fast is about to require two .next hops, and either one can blow up if the node in front of it is None.
  3. Move the tortoise one step: slow = slow.next.
  4. Move the hare two steps: fast = fast.next.next โ€” the first hop lands on fast.next (already known not to be None from the guard), the second hop is only safe because that node was reachable through a real node too.
  5. Compare the two pointers by identity right after moving them: if slow is fast, they now sit on the exact same node โ€” that can only happen if some path led fast back around to a node slow already occupies, i.e. a cycle. Return True immediately.
  6. If they're not on the same node yet, go back to step 2 and take another turn โ€” the loop guard re-checks fast and fast.next before either pointer moves again.
  7. If the while condition eventually fails โ€” fast lands on None, or fast.next is None โ€” the list ran out of nodes without slow and fast ever coinciding, so there's no cycle: return False.

Qanday Ishlaydi

  1. Ikkita pointer'ni list boshiga qo'ying: slow = head, fast = head. Ikkalasi bitta node'dan boshlanishi muhim emas โ€” ahamiyatli solishtirish ular harakatlangandan keyin sodir bo'ladi.
  2. fastga tegishdan oldin loop shartini tekshiring: while fast is not None and fast.next is not None. Ikkala qism ham muhim โ€” fastni siljitish ikkita .next qadamini talab qiladi, va agar oldingi node None bo'lsa, ulardan har biri xatoga olib kelishi mumkin.
  3. Toshbaqani bir qadam siljiting: slow = slow.next.
  4. Quyonni ikki qadam siljiting: fast = fast.next.next โ€” birinchi sakrash fast.nextga tushadi (guard orqali None emasligi allaqachon ma'lum), ikkinchi sakrash esa o'sha node ham haqiqiy node orqali yetib bo'lgani uchungina xavfsiz.
  5. Ikkala pointer'ni harakatlangandan darhol so'ng identity bo'yicha solishtiring: agar slow is fast bo'lsa, ular endi aynan bitta node'da turibdi โ€” bu faqat fast biror yo'l orqali slow allaqachon turgan node'ga aylanib qaytganda, ya'ni cycle mavjud bo'lgandagina sodir bo'lishi mumkin. Darhol True qaytaring.
  6. Agar ular hali bir xil node'da bo'lmasa, 2-qadamga qayting va yana bir navbat oling โ€” loop shartida har ikkala pointer harakatlanishidan oldin fast va fast.next qayta tekshiriladi.
  7. Agar oxir-oqibat while sharti bajarilmasa โ€” fast Nonega yetsa yoki fast.next None bo'lsa โ€” list slow va fast hech qachon bir-biriga to'g'ri kelmasdan tugagan, demak cycle yo'q: False qaytaring.

Complexity

Time: O(n). Consider two cases. If there's no cycle, fast is racing toward the end of the list at twice slow's speed, so it reaches the last node (or None) after roughly n/2 steps โ€” a single pass, linear in the number of nodes. If there is a cycle, once both pointers have entered the loop (which takes at most n steps for the slower one to get there), the gap between them shrinks by exactly one node every step โ€” fast covers 2 nodes, slow covers 1, so the net change in the gap is 1 per step โ€” so a gap that starts somewhere between 0 and the cycle's length must close to zero within at most that many additional steps. Either way, the total number of steps is bounded by a small multiple of n, so runtime is O(n) โ€” no nested loop, no revisiting the same ground more than a constant number of times.

Space: O(1). The algorithm only ever holds two pointer variables, slow and fast โ€” it never builds a set of nodes it's already seen the way a hash-set-based cycle check would, so memory use doesn't grow no matter how long the list โ€” or its cycle โ€” is.

Murakkablik

Vaqt: O(n). Ikki holatni ko'rib chiqing. Agar cycle bo'lmasa, fast list oxiriga slow'dan ikki baravar tez tomon yuguradi, shuning uchun taxminan n/2 qadamdan so'ng oxirgi node'ga (yoki Nonega) yetadi โ€” bitta pass, node'lar soniga nisbatan chiziqli. Agar cycle bo'lsa, ikkala pointer loop'ga kirgandan so'ng (buning uchun sekinroq pointer'ga ko'pi bilan n qadam kerak), ular orasidagi masofa har qadamda aynan bitta node'ga qisqaradi โ€” fast 2 ta, slow 1 ta node bosib o'tadi, shuning uchun masofaning sof o'zgarishi har qadamda 1 ta โ€” demak, 0 bilan cycle uzunligi orasida boshlangan masofa ko'pi bilan shuncha qo'shimcha qadamda nolga tushishi shart. Ikkala holatda ham jami qadamlar soni n'ning kichik karralisi bilan chegaralangan, shuning uchun runtime O(n) โ€” nested loop yo'q, bir xil joy bir necha martadan ortiq (o'zgarmas songa qadar) qayta bosib o'tilmaydi.

Xotira: O(1). Algoritm faqat ikkita pointer o'zgaruvchisini โ€” slow va fastni โ€” saqlaydi; u hech qachon ko'rilgan node'lar to'plamini (hash-set asosidagi cycle tekshiruvi qiladigandek) qurmaydi, shuning uchun list โ€” yoki uning cycle'i โ€” qanchalik uzun bo'lishidan qat'i nazar xotira sarfi o'smaydi.

Common Mistakes

  • Checking only while fast: and then running fast = fast.next.next unconditionally. Fix: guard both hops with while fast and fast.next: โ€” if fast has already reached the last node, fast.next is None, and fast.next.next tries to read .next off None, crashing with an AttributeError before the loop even gets to compare pointers.
  • Comparing slow is fast before either pointer has moved at all โ€” since both start at head, that condition is trivially true on entry, which would report a cycle even on a list that plainly doesn't have one. Fix: only compare after advancing both pointers inside the loop body, never before the first move.
  • Comparing node values (slow.val == fast.val) instead of comparing the nodes themselves. Fix: compare identity โ€” slow is fast โ€” because two distinct nodes can legitimately hold equal values without being the same node, which would produce a false positive.
  • Picking mismatched speeds โ€” e.g. advancing fast by one node just like slow, or by three nodes instead of two. Fix: keep the classic 1-step/2-step ratio; it's exactly what guarantees the gap between the pointers shrinks by one node every step once both are inside a cycle, which is what forces them to land on the same node instead of skipping past each other.

Ko'p Uchraydigan Xatolar

  • Faqat while fast:ni tekshirib, keyin fast = fast.next.nextni shartsiz bajarish. Yechim: ikkala sakrashni ham while fast and fast.next: bilan qo'riqlang โ€” agar fast allaqachon oxirgi node'ga yetgan bo'lsa, fast.next None bo'ladi, va fast.next.next Nonening .nextini o'qishga urinib, loop pointer'larni solishtirishga yetmasdanoq AttributeError bilan qulaydi.
  • Hech qaysi pointer harakatlanmasdan turib slow is fastni solishtirish โ€” ikkalasi ham headdan boshlangani uchun bu shart kirishda avtomatik to'g'ri bo'ladi, bu esa aslida cycle bo'lmagan list'da ham cycle borligini xabar qiladi. Yechim: faqat loop tanasida ikkala pointer ham harakatlangandan keyin solishtiring, birinchi harakatdan oldin hech qachon emas.
  • Node'larning o'zini emas, ularning qiymatlarini (slow.val == fast.val) solishtirish. Yechim: identity'ni solishtiring โ€” slow is fast โ€” chunki ikkita alohida node bir xil node bo'lmasdan ham qonuniy ravishda teng qiymatlarga ega bo'lishi mumkin, bu esa noto'g'ri musbat natija beradi.
  • Mos kelmaydigan tezliklarni tanlash โ€” masalan, fastni ham slow kabi bitta node siljitish, yoki ikkita o'rniga uchta node siljitish. Yechim: klassik 1-qadam/2-qadam nisbatini saqlang; aynan shu nisbat ikkala pointer cycle ichiga kirgandan so'ng ular orasidagi masofa har qadamda bitta node'ga qisqarishini kafolatlaydi โ€” bu esa ularni bir-birini o'tkazib yubormasdan, aynan bitta node'da uchrashishga majbur qiladi.

When to Use It

  • The problem involves a linked list (or anything shaped like one) and asks whether it loops back on itself โ€” "does this list have a cycle", "where does the cycle begin" โ€” without allowing extra memory for a visited set.
  • The problem explicitly wants O(1) extra space, ruling out the simpler hash-set-of-visited-nodes approach; fast & slow pointers is the standard way to trade that memory for pointer arithmetic.
  • You need the middle of a list in a single pass (LC 876): run both pointers at the usual 1x/2x speeds with no cycle check at all โ€” when fast reaches the end, slow is standing exactly halfway.
  • The sequence isn't a literal linked list but is generated by repeatedly applying some "next" function to a value (e.g. LC 202 Happy Number, where "next" means "sum the squares of the digits") โ€” any such sequence either reaches a fixed terminal state or falls into a repeating loop, and fast & slow pointers detects which, without storing every value seen.

Qachon Ishlatish Kerak

  • Masala linked list'ga (yoki shunga o'xshash narsaga) tegishli va list o'ziga qaytib loop hosil qiladimi โ€” "bu list'da cycle bormi", "cycle qayerdan boshlanadi" โ€” degan savol beradi, va qo'shimcha xotirada visited-set saqlashga ruxsat bermaydi.
  • Masala aniq O(1) qo'shimcha xotira talab qiladi, bu esa oddiyroq hash-set-of-visited-nodes yondashuvini istisno qiladi; fast & slow pointers o'sha xotirani pointer arifmetikasiga almashtirishning standart usuli.
  • Sizga bitta pass'da list'ning o'rtasi kerak (LC 876): hech qanday cycle tekshiruvisiz, ikkala pointer'ni odatdagi 1x/2x tezlikda yurgizing โ€” fast oxiriga yetganda, slow aynan yarmida turadi.
  • Ketma-ketlik aslida linked list emas, balki biror qiymatga "next" funksiyasini qayta-qayta qo'llash orqali hosil bo'ladi (masalan, LC 202 Happy Number, bu yerda "next" โ€” raqamlar kvadratlarining yig'indisi degani) โ€” bunday har qanday ketma-ketlik yo qat'iy yakuniy holatga yetadi, yoki takrorlanuvchi loop'ga tushadi, va fast & slow pointers har bir ko'rilgan qiymatni saqlamasdan qaysi biri ekanligini aniqlaydi.

LeetCode Practice

141. Linked List Cycle โ†—

Restated: given the head of a singly linked list, determine if the list has a cycle in it โ€” meaning some node's next pointer eventually points back to a node earlier in the list, so the list never actually ends. Return True if there's a cycle, False otherwise, using only O(1) extra memory (no set of every node visited).

  1. Spot the shape. "Does this list loop back on itself" plus the O(1)-memory requirement is exactly the fast & slow pointers pattern โ€” a hash set of visited nodes would also work, but costs O(n) space, which the constraint rules out.
  2. No special-case code needed up front. Start both pointers at head. If the list is empty or has a single node with no self-loop, the loop guard fast and fast.next is simply false immediately (or after one check), so there's nothing extra to handle before the loop.
  3. Advance at the classic ratio. Every turn, move slow one node and fast two nodes, guarded by while fast and fast.next.
  4. Check for a meeting after every advance. If slow is fast, a cycle exists โ€” return True right away, no need to keep going.
  5. Let the guard end the search naturally. If fast (or fast.next) ever becomes None, the list ran out of nodes without a meeting, so return False.
def hasCycle(head):
    # head is a ListNode (or None); ListNode has .val and .next,
    # as provided by LeetCode's linked list definition.
    slow, fast = head, head

    while fast and fast.next:
        slow = slow.next        # tortoise: one node per step
        fast = fast.next.next   # hare: two nodes per step

        if slow is fast:        # they can only coincide if fast looped back onto slow
            return True

    return False  # fast (or fast.next) hit None: list has a real end, no cycle

LeetCode Amaliyoti

141. Linked List Cycle โ†—

Qayta bayon: singly linked list'ning headi berilgan โ€” list'da cycle bor-yo'qligini aniqlang, ya'ni biror node'ning next pointer'i oxir-oqibat list ichidagi avvalgi node'ga qaytib bog'lanadimi, shu sababli list aslida hech qachon tugamaydimi. Cycle bo'lsa True, bo'lmasa False qaytaring โ€” faqat O(1) qo'shimcha xotira bilan (ko'rilgan har bir node'ning to'plami saqlanmasdan).

  1. Shaklni tanib oling. "Bu list o'ziga qaytib loop hosil qiladimi" degan savol, O(1)-xotira talabi bilan birga, aynan fast & slow pointers pattern'i โ€” ko'rilgan node'lar hash set'i ham ishlagan bo'lardi, lekin u O(n) xotira sarflaydi, bu esa shart tomonidan istisno qilingan.
  2. Oldindan maxsus holat kodi kerak emas. Ikkala pointer'ni ham headdan boshlang. Agar list bo'sh bo'lsa yoki o'z-o'ziga loop qilmaydigan bitta node'dan iborat bo'lsa, fast and fast.next loop shartning o'zi darhol (yoki bir tekshiruvdan keyin) noto'g'ri bo'ladi, shuning uchun loop'dan oldin qo'shimcha hech narsa hal qilish shart emas.
  3. Klassik nisbat bilan siljiting. Har navbatda slowni bitta node, fastni ikkita node siljiting, while fast and fast.next bilan qo'riqlangan holda.
  4. Har bir siljishdan keyin uchrashuvni tekshiring. Agar slow is fast bo'lsa, cycle mavjud โ€” darhol True qaytaring, davom etish shart emas.
  5. Qidiruvni guard'ning o'zi tabiiy tugatishiga ruxsat bering. Agar fast (yoki fast.next) qachondir None bo'lib qolsa, list uchrashuvsiz tugagan, demak False qaytaring.
def hasCycle(head):
    # head is a ListNode (or None); ListNode has .val and .next,
    # as provided by LeetCode's linked list definition.
    slow, fast = head, head

    while fast and fast.next:
        slow = slow.next        # tortoise: one node per step
        fast = fast.next.next   # hare: two nodes per step

        if slow is fast:        # they can only coincide if fast looped back onto slow
            return True

    return False  # fast (or fast.next) hit None: list has a real end, no cycle

Check Yourself

O'zingizni Sinang

ยฉ 2026 Davronbek