diff --git a/DIRECTORY.md b/DIRECTORY.md index a7a1e1b5..247db1bb 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -69,6 +69,7 @@ * [Circular Linked List](https://github.com/TheAlgorithms/R/blob/HEAD/linked_list_algorithms/circular_linked_list.r) * [Doubly Linked List](https://github.com/TheAlgorithms/R/blob/HEAD/linked_list_algorithms/doubly_linked_list.r) * [Singly Linked List](https://github.com/TheAlgorithms/R/blob/HEAD/linked_list_algorithms/singly_linked_list.r) + * [Two-Pointer Technique (Fast and Slow Pointers)](https://github.com/TheAlgorithms/R/blob/HEAD/linked_list_algorithms/two_pointer_technique.r) ## Machine Learning * [Gradient Boosting](https://github.com/TheAlgorithms/R/blob/HEAD/machine_learning/gradient_boosting.r) diff --git a/linked_list_algorithms/two_pointer_technique.r b/linked_list_algorithms/two_pointer_technique.r new file mode 100644 index 00000000..6facf0c4 --- /dev/null +++ b/linked_list_algorithms/two_pointer_technique.r @@ -0,0 +1,133 @@ +# 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") +} \ No newline at end of file