RedEnginePress logo
RedEnginePress
AlgorithmsLanguagesPlaygroundAbout

Two Pointer Technique

S
# Two-Pointer Technique (Fast and Slow Pointers / Tortoise and Hare)
#
# Template and practical application for singly linked lists using the
# fast & slow pointer technique. This file provides:
# - A minimal Node reference-class local to this file
# - Generic helper: build list from a vector
# - detect_cycle(head): returns TRUE/FALSE using tortoise & hare
# - find_middle(head): returns the middle node's data (for even length returns the second middle)
# - nth_from_end(head, n): returns the data of the Nth node from the end (1-based)
#
# Practical application demonstrated: finding the Nth node from the end.

TwoPtrNode <- setRefClass("TwoPtrNode",
  fields = list(
    data = "ANY",
    next_node = "ANY"
  ),
  methods = list(
    initialize = function(data = NULL, next_node = NULL) {
      .self$data <- data
      .self$next_node <- next_node
    },

    print = function() {
      cat("Node(data =", .self$data, ")\n")
    }
  )
)

# Build a singly linked list from an R vector of values. Returns the head node.
build_list_from_vector <- function(vec) {
  if (length(vec) == 0) return(NULL)
  head <- TwoPtrNode$new(data = vec[1])
  current <- head
  if (length(vec) > 1) {
    for (v in vec[-1]) {
      new_node <- TwoPtrNode$new(data = v)
      current$next_node <- new_node
      current <- new_node
    }
  }
  return(head)
}

# Convert linked list to vector for easy printing/inspection
# This is defensive against cycles by truncating after `max_nodes` items.
list_to_vector <- function(head, max_nodes = 1000) {
  out <- c()
  cur <- head
  i <- 0
  while (!is.null(cur) && i < max_nodes) {
    out <- c(out, cur$data)
    cur <- cur$next_node
    i <- i + 1
  }
  if (!is.null(cur)) {
    out <- c(out, "... (truncated or cycle detected)")
  }
  return(out)
}

# Detect cycle using fast and slow pointers (Tortoise & Hare)
detect_cycle <- function(head) {
  if (is.null(head)) return(FALSE)
  slow <- head
  fast <- head
  while (!is.null(fast) && !is.null(fast$next_node)) {
    slow <- slow$next_node
    fast <- fast$next_node$next_node
    if (identical(slow, fast)) return(TRUE)
  }
  return(FALSE)
}

# Find middle node using two pointers. For even-length lists this returns
# the second middle (i.e., for 1->2->3->4 it returns 3).
find_middle <- function(head) {
  if (is.null(head)) return(NULL)
  slow <- head
  fast <- head
  while (!is.null(fast) && !is.null(fast$next_node)) {
    slow <- slow$next_node
    fast <- fast$next_node$next_node
  }
  return(slow)
}

# Return the data of the Nth node from the end (1-based). Throws an error
# if n is invalid or greater than list length.
nth_from_end <- function(head, n) {
  if (is.null(head)) stop("List is empty")
  if (n <= 0) stop("n must be a positive integer")

  fast <- head
  # Advance fast by n steps
  for (i in seq_len(n)) {
    if (is.null(fast)) stop(sprintf("n (%d) is larger than the list length", n))
    fast <- fast$next_node
  }

  slow <- head
  # Move both until fast is NULL; slow will be at the Nth from end
  while (!is.null(fast)) {
    slow <- slow$next_node
    fast <- fast$next_node
  }
  return(slow$data)
}

# ---- Practical demonstration ----
if (sys.nframe() == 0) {
  cat("Two-Pointer Technique demo\n")
  head <- build_list_from_vector(1:7)
  cat("List:", paste(list_to_vector(head), collapse = " -> "), "\n")

  mid <- find_middle(head)
  cat("Middle node data:", ifelse(is.null(mid), "NULL", mid$data), "\n")

  n <- 2
  nth <- nth_from_end(head, n)
  cat(sprintf("%d-th node from the end: %s\n", n, nth))

  cat("Cycle detected?", detect_cycle(head), "\n")

  # Create a cycle for testing (connect tail to node with data=3)
  tail <- head
  while (!is.null(tail$next_node)) tail <- tail$next_node
  cur <- head
  while (!is.null(cur) && cur$data != 3) cur <- cur$next_node
  if (!is.null(cur)) tail$next_node <- cur
  cat("After creating a cycle (tail -> node with data 3):\n")
  cat("Cycle detected?", detect_cycle(head), "\n")
}