Linked List Loop

  • Given a singly linked list, determine if it contains a cycle
  • A cycle occurs if a node’s next pointer reference an earlier node in the list (causing a loop)

The point:

  • Use 2 ptrs
  • One fast and one slow
  • fast move 2 steps at a time (fast = fast.next.next)
  • fast ptr will always catch up slow ptr. worst case : k, length of the cycle

If no cycle, fast leave the linked list

Complexity :

Time Space
O(n) O(1)

About Rust :

  • Naïve and not naïve implementations are demonstrated
  • If there is a loop in the list => 2 pointers pointing to the same cell => Rc
  • RefCell supports interior mutability. This is a way of bypassing Rust’s borrowing rules at runtime rather than at compile-time.
  • Without RefCell, an Rc cannot be modified. Rust forbids to modify a structure contained in an Rc. An Rc only gives (immutable) read access to the underlying data. See :
tail.borrow_mut().next = Some(Rc::clone(target)); 
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::HashSet;

type Link = Option<Rc<RefCell<Node>>>; 

struct Node {
    val: i32,
    next: Link, // use the alias here for clarity
}

impl Node {
    fn new(val: i32, next: Link) -> Rc<RefCell<Node>> {
        Rc::new(RefCell::new(Self { val, next }))
    }
}

fn linked_list_loop_naive(head : &Link)->bool{
    let mut visited = HashSet::new(); // O(n) space
    let mut current = head.as_ref().map(Rc::clone);
    while let Some(node_rc) = current {
        let addr = Rc::as_ptr(&node_rc) as usize;
        if visited.contains(&addr){
            return true;
        }
        visited.insert(addr);
        current = node_rc.borrow().next.as_ref().map(Rc::clone);
    }
    false
}



fn linked_list_loop(head: &Link) -> bool {
    let mut slow = head.as_ref().map(Rc::clone);
    let mut fast = head.as_ref().map(Rc::clone);

    while let (Some(s_node), Some(f_node)) = (&slow, &fast) {
        // Advance slow one step
        let next_slow = s_node.borrow().next.as_ref().map(Rc::clone);

        // Advance fast two steps
        let next_fast_1 = f_node.borrow().next.as_ref().map(Rc::clone);
        let next_fast_2 = match next_fast_1 {
            Some(ref next) => next.borrow().next.as_ref().map(Rc::clone),
            None => None,
        };

        slow = next_slow;
        fast = next_fast_2;

        // Check for pointer equality (cycle detection)
        if let (Some(s_ptr), Some(f_ptr)) = (&slow, &fast) {
            if Rc::ptr_eq(s_ptr, f_ptr) {
                return true;
            }
        }
    }
    false
}

fn main(){     // no main() if this code runs in a Jupyter cell 
// {                 // local scope to avoid issue with the lifetime of head during borrow

    let mut head = None; // Start with an empty list (head is None)
    let vals = vec![0, 1, 2, 3, 4, 5];
    let mut nodes = vec![];  // Keep references to all nodes in a vector for later access
    for v in vals.into_iter().rev() {
        let new_node = Node::new(v, head.take());
        head = Some(Rc::clone(&new_node));
        nodes.push(new_node);
    }
    println!("{}", linked_list_loop_naive(&head));
    println!("{}", linked_list_loop(&head));

    let tail = &nodes[0]; // node 5
    let target = &nodes[2]; // node 3
    tail.borrow_mut().next = Some(Rc::clone(target)); // cycle: 5 → 3
    println!("{}", linked_list_loop_naive(&head));
    println!("{}", linked_list_loop(&head));
} // end of local scope OR end of main()    

false
false
true
true





()

Back to top

Published on: Jun 22 2025 at 09:00 AM | Last updated: Jun 22 2025 at 09:00 AM

Copyright © 1964-2025 - 40tude