1+ # Two-Pointer Technique (Fast and Slow Pointers / Tortoise and Hare)
2+ #
3+ # Template and practical application for singly linked lists using the
4+ # fast & slow pointer technique. This file provides:
5+ # - A minimal Node reference-class local to this file
6+ # - Generic helper: build list from a vector
7+ # - detect_cycle(head): returns TRUE/FALSE using tortoise & hare
8+ # - find_middle(head): returns the middle node's data (for even length returns the second middle)
9+ # - nth_from_end(head, n): returns the data of the Nth node from the end (1-based)
10+ #
11+ # Practical application demonstrated: finding the Nth node from the end.
12+
13+ TwoPtrNode <- setRefClass(" TwoPtrNode" ,
14+ fields = list (
15+ data = " ANY" ,
16+ next_node = " ANY"
17+ ),
18+ methods = list (
19+ initialize = function (data = NULL , next_node = NULL ) {
20+ .self $ data <- data
21+ .self $ next_node <- next_node
22+ },
23+
24+ print = function () {
25+ cat(" Node(data =" , .self $ data , " )\n " )
26+ }
27+ )
28+ )
29+
30+ # Build a singly linked list from an R vector of values. Returns the head node.
31+ build_list_from_vector <- function (vec ) {
32+ if (length(vec ) == 0 ) return (NULL )
33+ head <- TwoPtrNode $ new(data = vec [1 ])
34+ current <- head
35+ if (length(vec ) > 1 ) {
36+ for (v in vec [- 1 ]) {
37+ new_node <- TwoPtrNode $ new(data = v )
38+ current $ next_node <- new_node
39+ current <- new_node
40+ }
41+ }
42+ return (head )
43+ }
44+
45+ # Convert linked list to vector for easy printing/inspection
46+ # This is defensive against cycles by truncating after `max_nodes` items.
47+ list_to_vector <- function (head , max_nodes = 1000 ) {
48+ out <- c()
49+ cur <- head
50+ i <- 0
51+ while (! is.null(cur ) && i < max_nodes ) {
52+ out <- c(out , cur $ data )
53+ cur <- cur $ next_node
54+ i <- i + 1
55+ }
56+ if (! is.null(cur )) {
57+ out <- c(out , " ... (truncated or cycle detected)" )
58+ }
59+ return (out )
60+ }
61+
62+ # Detect cycle using fast and slow pointers (Tortoise & Hare)
63+ detect_cycle <- function (head ) {
64+ if (is.null(head )) return (FALSE )
65+ slow <- head
66+ fast <- head
67+ while (! is.null(fast ) && ! is.null(fast $ next_node )) {
68+ slow <- slow $ next_node
69+ fast <- fast $ next_node $ next_node
70+ if (identical(slow , fast )) return (TRUE )
71+ }
72+ return (FALSE )
73+ }
74+
75+ # Find middle node using two pointers. For even-length lists this returns
76+ # the second middle (i.e., for 1->2->3->4 it returns 3).
77+ find_middle <- function (head ) {
78+ if (is.null(head )) return (NULL )
79+ slow <- head
80+ fast <- head
81+ while (! is.null(fast ) && ! is.null(fast $ next_node )) {
82+ slow <- slow $ next_node
83+ fast <- fast $ next_node $ next_node
84+ }
85+ return (slow )
86+ }
87+
88+ # Return the data of the Nth node from the end (1-based). Throws an error
89+ # if n is invalid or greater than list length.
90+ nth_from_end <- function (head , n ) {
91+ if (is.null(head )) stop(" List is empty" )
92+ if (n < = 0 ) stop(" n must be a positive integer" )
93+
94+ fast <- head
95+ # Advance fast by n steps
96+ for (i in seq_len(n )) {
97+ if (is.null(fast )) stop(sprintf(" n (%d) is larger than the list length" , n ))
98+ fast <- fast $ next_node
99+ }
100+
101+ slow <- head
102+ # Move both until fast is NULL; slow will be at the Nth from end
103+ while (! is.null(fast )) {
104+ slow <- slow $ next_node
105+ fast <- fast $ next_node
106+ }
107+ return (slow $ data )
108+ }
109+
110+ # ---- Practical demonstration ----
111+ if (sys.nframe() == 0 ) {
112+ cat(" Two-Pointer Technique demo\n " )
113+ head <- build_list_from_vector(1 : 7 )
114+ cat(" List:" , paste(list_to_vector(head ), collapse = " -> " ), " \n " )
115+
116+ mid <- find_middle(head )
117+ cat(" Middle node data:" , ifelse(is.null(mid ), " NULL" , mid $ data ), " \n " )
118+
119+ n <- 2
120+ nth <- nth_from_end(head , n )
121+ cat(sprintf(" %d-th node from the end: %s\n " , n , nth ))
122+
123+ cat(" Cycle detected?" , detect_cycle(head ), " \n " )
124+
125+ # Create a cycle for testing (connect tail to node with data=3)
126+ tail <- head
127+ while (! is.null(tail $ next_node )) tail <- tail $ next_node
128+ cur <- head
129+ while (! is.null(cur ) && cur $ data != 3 ) cur <- cur $ next_node
130+ if (! is.null(cur )) tail $ next_node <- cur
131+ cat(" After creating a cycle (tail -> node with data 3):\n " )
132+ cat(" Cycle detected?" , detect_cycle(head ), " \n " )
133+ }
0 commit comments