Two Pointer Technique Explained — Patterns, Pitfalls and Interview Wins
- Two pointer technique trades a second loop for a second index variable — same information, O(n) instead of O(n²). The decision rule that moves each pointer is the entire algorithm's intelligence.
- The opposite-ends pattern requires a sorted array because sorting is what makes the 'move left if sum is too small, move right if too large' rule provably correct — without order, there's no safe direction to move.
- The sliding window is two pointers in the same direction with a maintained state (HashMap, counter, running sum) between them. The right pointer always expands; the left pointer only shrinks when a constraint is violated.
- Opposite ends: left at 0, right at n-1, move inward. Requires sorted input.
- Slow-fast: one pointer reads, one writes. Used for in-place filtering and cycle detection.
- Sliding window: both move same direction, window expands/contracts based on constraint.
- Replacing nested loops with two pointers on a 10M-element dataset drops runtime from hours to seconds.
- The decision rule that moves each pointer is the entire algorithm's intelligence.
- Using opposite-ends on unsorted input. The move direction rule breaks and valid pairs are silently missed.
Two pointer returns wrong answer on sorted input.
System.out.println(Arrays.toString(arr)) — verify array is actually sortedAdd debug logging inside the while loop: print left, right, and current sum on each iterationSliding window returns wrong length.
Print windowStart and windowEnd on each iteration to trace the window boundariesPrint lastSeenAtIndex.get(currentChar) to see if it is before windowStartIn-place removal leaves garbage values.
Print the returned newLength and compare to array.lengthPrint Arrays.toString(arr, 0, newLength) — only the valid portionAlgorithm is O(n²) despite using two pointers.
Add a counter incremented inside the while loop and print it after the algorithm finishesCompare counter to n — if counter is O(n²), the window logic has redundant scansProduction Incident
Production Debug GuideSymptom-first investigation path for two pointer and sliding window problems.
Arrays.sort() or switch to a HashMap-based approach if original indices must be preserved.Arrays and strings are everywhere — sorted contact lists, sliding windows in streaming data, palindrome checks in form validation. The naive solution to most array problems is a nested loop: check every pair, every substring, every combination. That works on small inputs, but nested loops mean O(n²) time complexity. Feed them a million-element array and your program grinds to a halt. Interviews notice this immediately, and so do production systems.
The two pointer technique exists to kill that O(n²) cost. Instead of two loops, you place two index variables — pointers — at strategic positions in the array or string and move them toward each other (or in the same direction) based on simple logic. You scan the data once, or close to it, dropping complexity to O(n). The elegance isn't just academic: it maps directly to real-world patterns like merging sorted datasets, detecting cycles, and finding longest substrings with constraints.
By the end of this article you'll understand exactly why the two pointer approach works, how to recognise problems that beg for it, and how to implement the three core variants — opposite ends, same direction (slow/fast), and sliding window — with complete, runnable Java code. You'll also know the exact mistakes that trip up intermediate developers so you don't repeat them.
How Two Pointers Work — Plain English and Patterns
The two-pointer technique uses two index variables that move through an array (or string) to avoid an O(n^2) nested loop. Depending on the pattern, the pointers either start at opposite ends and move toward each other, or both start at the left and one chases the other.
Pattern 1 — Opposite ends (sorted array, palindrome check): 1. Set left=0, right=n-1. 2. While left < right: examine arr[left] and arr[right]. 3. Based on their values or sum, move left right or move right left. 4. Stop when pointers meet.
Worked example — Two Sum in sorted array [1,2,3,4,6], target=6: left=0(1), right=4(6). sum=7 > 6. Move right left. right=3(4). left=0(1), right=3(4). sum=5 < 6. Move left right. left=1(2). left=1(2), right=3(4). sum=6 == 6. Found! Indices (1,3).
Pattern 2 — Fast/slow (remove duplicates, partition): 1. slow=0 marks the boundary of the 'good' region. 2. fast scans ahead. 3. When fast finds something useful, copy to slow, increment slow.
Time: O(n) — each pointer traverses at most n elements total.
package io.thecodeforge.algo; /** * Summary of the three core two-pointer patterns. * Each pattern reduces O(n²) nested loops to O(n) single-pass logic. */ public class TwoPointerPatternSummary { /* * PATTERN 1: Opposite Ends * ─────────────────────: left=0, right=n-1 * Movement: move left right or right left based on comparison * Requirement: sorted array (for numeric problems) * Time: O(n), Space: O(1) * Examples: Two Sum II, Container With Most Water, Valid Palindrome */ /* * PATTERN 2: Slow-Fast (Read-Write) * ────────────────────────────────── * Setup: slow=0 (write position), fast=0 (scanner) * Movement: fast always advances. slow advances only when a valid element is found. * Requirement: none (works on unsorted input) * Time: O(n), Space: O(1) * Examples: Remove Duplicates, Remove Element, Move Zeroes */ /* * P─── * SetupATTERN 3: Sliding Window * ───────────────────────── * Setup: left=0, right=0 * Movement: right always advances. left advances when constraint is violated. * Requirement: constraint must be incrementally maintainable (HashSet, counter, sum) * Time: O(n), Space: O(k) where k is alphabet or constraint size * Examples: Longest Substring Without Repeating, Minimum Window Substring */ public static void main(String[] args) { System.out.println("Two Pointer Patterns:"); System.out.println("1. Opposite Ends — sorted arrays, pair/triplet finding"); System.out.println("2. Slow-Fast — in-place filtering, cycle detection"); System.out.println("3. Sliding Window — substring/subarray optimization"); System.out.println("\nAll three reduce O(n²) to O(n) by eliminating redundant comparisons."); } }
1. Opposite Ends — sorted arrays, pair/triplet finding
2. Slow-Fast — in-place filtering, cycle detection
3. Sliding Window — substring/subarray optimization
All three reduce O(n²) to O(n) by eliminating redundant comparisons.
- Opposite ends: exploit sorted order. Sum too big? All pairs with this right value are too big. Skip them all.
- Slow-fast: exploit the read-write separation. Scanner finds, writer places. No redundant scans.
- Sliding window: exploit constraint monotonicity. Constraint violated? Shrink left. Never re-check right.
- All three patterns share: each element is visited at most twice. This is what makes them O(n).
- The decision rule must be provably correct. If you cannot prove it skips only invalid answers, the algorithm is wrong.
The Opposite-Ends Pattern — Two Sum on a Sorted Array
The most classic two pointer setup: left pointer at index 0, right pointer at index n-1, both moving inward. This works exclusively because the array is sorted — that ordering gives you a decision rule. If the sum of the two pointed elements is too large, you need a smaller right value, so move right inward. Too small? Move left outward. Equal? You found your answer.
Why does this never miss the correct pair? Because at every step you're eliminating an entire row or column of the conceptual 'pairs grid' based on a provably safe rule. You're not guessing; you're reasoning. That's the mental model to hold onto.
This pattern appears in: Two Sum II (sorted input), container with most water, 3Sum (fix one element, run two pointers on the rest), trapping rainwater, and valid palindrome checks. If the problem involves a sorted array and asks you to find a pair (or triplet) satisfying a numeric condition, reach for opposite-end pointers first.
package io.thecodeforge.algo; public class TwoSumSortedArray { /** * Finds indices of two numbers in a sorted array that add up to the target. * Uses opposite-end two pointers — O(n) time, O(1) space. */ public static int[] findPairWithTargetSum(int[] sortedNumbers, int target) { int leftIndex = 0; int rightIndex = sortedNumbers.length - 1; while (leftIndex < rightIndex) { int currentSum = sortedNumbers[leftIndex] + sortedNumbers[rightIndex]; if (currentSum == target) { return new int[]{leftIndex + 1, rightIndex + 1}; } else if (currentSum < target) { leftIndex++; } else { rightIndex--; } } return new int[]{-1, -1}; } public static void main(String[] args) { int[] bookPageCounts = {120, 230, 280, 370, 450, 500, 620}; int targetPages = 750; int[] result = findPairWithTargetSum(bookPageCounts, targetPages); if (result[0] != -1) { System.out.println("Pair found at positions " + result[0] + " and " + result[1]); System.out.println("Values: " + bookPageCounts[result[0] - 1] + " + " + bookPageCounts[result[1] - 1] + " = " + targetPages); } else { System.out.println("No pair adds up to " + targetPages); } int[] noResult = findPairWithTargetSum(bookPageCounts, 9999); System.out.println("\nImpossible target result: [" + noResult[0] + ", " + noResult[1] + "]"); } }
Values: 280 + 500 = 750
Impossible target result: [-1, -1]
The Slow-Fast Pointer Pattern — Valid Palindrome and In-Place Removal
Sometimes both pointers start at the same end but move at different speeds, or they serve different roles: one 'reads', the other 'writes'. This is the slow-fast (or read-write) variant.
For palindrome detection, start both pointers at opposite ends and walk inward, skipping non-alphanumeric characters. The twist here is conditional movement — each pointer jumps independently based on what character it's currently pointing at. This feels different from the Two Sum pattern but uses the same 'two index variables, one pass' skeleton.
For in-place array manipulation — like removing duplicates or filtering out a value — a slow pointer marks the 'write position' while a fast pointer scans ahead. The fast pointer finds valid elements and hands them to the slow pointer to place. The array is rewritten in-place without extra space. This pattern appears in LeetCode problems 26, 27, 80, and is a favourite in interviews because it tests space-efficiency instincts.
package io.thecodeforge.algo; public class PalindromeAndInPlaceRemoval { // ── Part 1: Valid Palindrome (ignoring non-alphanumeric chars) ───────────── public static boolean isValidPalindrome(String sentence) { int leftIndex = 0; int rightIndex = sentence.length() - 1; while (leftIndex < rightIndex) { while (leftIndex < rightIndex && !Character.isLetterOrDigit(sentence.charAt(leftIndex))) { leftIndex++; } while (leftIndex < rightIndex && !Character.isLetterOrDigit(sentence.charAt(rightIndex))) { rightIndex--; } if (Character.toLowerCase(sentence.charAt(leftIndex)) != Character.toLowerCase(sentence.charAt(rightIndex))) { return false; } leftIndex++; rightIndex--; } return true; } // ── Part 2: Remove all instances of a value in-place ────────────────────── public static int removeValueInPlace(int[] numbers, int valueToRemove) { int writePosition = 0; for (int readPosition = 0; readPosition < numbers.length; readPosition++) { if (numbers[readPosition] != valueToRemove) { numbers[writePosition] = numbers[readPosition]; writePosition++; } } return writePosition; } public static void main(String[] args) { String phrase1 = "A man, a plan, a canal: Panama"; String phrase2 = "race a car"; System.out.println("'" + phrase1 + "' is palindrome: " + isValidPalindrome(phrase1)); System.out.println("'" + phrase2 + "' is palindrome: " + isValidPalindrome(phrase2)); int[] scores = {3, 2, 2, 3, 4, 3, 5}; int removeTarget = 3; System.out.println("\nOriginal scores array length: " + scores.length); int newLength = removeValueInPlace(scores, removeTarget); System.out.print("After removing all " + removeTarget + "s — "); System.out.print("new length: " + newLength + ", elements: "); for (int i = 0; i < newLength; i++) { System.out.print(scores[i] + (i < newLength - 1 ? ", " : "\n")); } } }
'race a car' is palindrome: false
Original scores array length: 7
After removing all 3s — new length: 4, elements: 2, 2, 4, 5
- Slow pointer (writePosition): marks the boundary of the 'clean' region.
- Fast pointer (readPosition): scans every element, forwarding valid ones to slow.
- Elements beyond writePosition are garbage — never read them.
- Time: O(n). Space: O(1). Each element is visited exactly once by the fast pointer.
- This pattern also works for linked list cycle detection (Floyd's algorithm).
The Sliding Window Pattern — Longest Substring Without Repeating Characters
Sliding window is the two pointer technique's grown-up sibling. Both pointers move in the same direction, but the window between them expands and contracts based on a constraint. This is perfect for substring and subarray problems where you're looking for the longest, shortest, or most optimal contiguous segment.
The key insight: instead of recomputing the state of every possible window from scratch (O(n²) or worse), you maintain a running state — a HashSet, frequency map, or running sum — and update it incrementally as the window slides. When the constraint is violated, shrink from the left. When it's satisfied, expand from the right.
Real-world analogy: think of a conveyor belt at a checkout. You look at items on the belt through a fixed viewport. If two identical items appear, you push the belt forward from behind until one falls off. You never re-scan items you've already processed. That's O(n) thinking.
package io.thecodeforge.algo; import java.util.HashMap; import java.util.Map; public class LongestUniqueSubstring { public static int longestNonRepeatingLength(String text) { Map<Character, Integer> lastSeenAtIndex = new HashMap<>(); int maxLength = 0; int windowStart = 0; for (int windowEnd = 0; windowEnd < text.length(); windowEnd++) { char currentChar = text.charAt(windowEnd); if (lastSeenAtIndex.containsKey(currentChar) && lastSeenAtIndex.get(currentChar) >= windowStart) { windowStart = lastSeenAtIndex.get(currentChar) + 1; } lastSeenAtIndex.put(currentChar, windowEnd); int currentWindowLength = windowEnd - windowStart + 1; maxLength = Math.max(maxLength, currentWindowLength); } return maxLength; } public static String longestNonRepeatingSubstring(String text) { Map<Character, Integer> lastSeenAtIndex = new HashMap<>(); int maxLength = 0; int windowStart = 0; int bestWindowStart = 0; for (int windowEnd = 0; windowEnd < text.length(); windowEnd++) { char currentChar = text.charAt(windowEnd); if (lastSeenAtIndex.containsKey(currentChar) && lastSeenAtIndex.get(currentChar) >= windowStart) { windowStart = lastSeenAtIndex.get(currentChar) + 1; } lastSeenAtIndex.put(currentChar, windowEnd); int currentWindowLength = windowEnd - windowStart + 1; if (currentWindowLength > maxLength) { maxLength = currentWindowLength; bestWindowStart = windowStart; } } return text.substring(bestWindowStart, bestWindowStart + maxLength); } public static void main(String[] args) { String[] testInputs = { "abcabcbb", "bbbbb", "pwwkew", "theCodeForge" }; for (String input : testInputs) { int length = longestNonRepeatingLength(input); String substring = longestNonRepeatingSubstring(input); System.out.printf("Input: %-15s → Length: %d, Substring: \"%s\"%n", "\"" + input + "\"", length, substring); } } }
Input: "bbbbb" → Length: 1, Substring: "b"
Input: "pwwkew" → Length: 3, Substring: "wke"
Input: "theCodeForge" → Length: 8, Substring: "heCodeFo"
- Without Math.max: windowStart jumps backward when a duplicate was seen before the window. Window grows incorrectly.
- With Math.max: windowStart never moves backward. Window only shrinks or stays.
- Edge case: 'abba'. Without Math.max, the second 'b' causes windowStart to jump to index 2, but the first 'a' at index 0 was seen before the window. Math.max prevents the backward jump.
- Sliding window invariant: windowStart only moves forward. Never backward. Math.max enforces this.
- This is the #1 bug in sliding window implementations. Test with 'abba' and 'tmmzuxt' to catch it.
Three Sum — Extending Two Pointers to Triplets
Three Sum extends the opposite-ends pattern: fix one element, then run two pointers on the remaining subarray to find a pair that sums to the negation of the fixed element. The outer loop is O(n), the inner two-pointer scan is O(n), giving O(n²) total — optimal for this problem.
The critical challenge is avoiding duplicate triplets. After sorting, duplicate values sit adjacent to each other. Without explicit skipping, the same triplet is found multiple times. The fix: after processing each element, skip forward past all identical values. Same logic applies inside the two-pointer loop after finding a valid pair.
This pattern generalizes to K Sum: fix K-2 elements with nested loops, then run two pointers on the remainder. Each additional fixed element adds an O(n) layer, so K Sum is O(n^(K-1)).
package io.thecodeforge.algo; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ThreeSumSolver { /** * Finds all unique triplets in the array that sum to zero. * Sort + fix one element + two pointers on remainder. * O(n²) time, O(1) space excluding output. */ public static List<int[]> findAllTripletsSummingToZero(int[] nums) { List<int[]> triplets = new ArrayList<>(); Arrays.sort(nums); // Sort first — enables two-pointer logic and duplicate skipping for (int i = 0; i < nums.length - 2; i++) { // Skip duplicate values for the fixed element if (i > 0 && nums[i] == nums[i - 1]) continue; // Early termination: if the smallest possible sum is > 0, no more triplets if (nums[i] > 0) break; int left = i + 1; int right = nums.length - 1; int target = -nums[i]; // We need nums[left] + nums[right] == -nums[i] while (left < right) { int sum = nums[left] + nums[right]; if (sum == target) { triplets.add(new int[]{nums[i], nums[left], nums[right]}); // Skip duplicates for left pointer while (left < right && nums[left] == nums[left + 1]) left++; // Skip duplicates for right pointer while (left < right && nums[right] == nums[right - 1]) right--; left++; right--; } else if (sum < target) { left++; } else { right--; } } } return triplets; } public static void main(String[] args) { int[] transactions = {-1, 0, 1, 2, -1, -4}; List<int[]> results = findAllTripletsSummingToZero(transactions); System.out.println("Triplets summing to zero:"); for (int[] triplet : results) { System.out.println(" [" + triplet[0] + ", " + triplet[1] + ", " + triplet[2] + "]"); } } }
[-1, -1, 2]
[-1, 0, 1]
- Outer loop: fix one element at index i. O(n) iterations.
- Inner loop: two pointers on subarray [i+1, n-1]. O(n) per iteration.
- Total: O(n²). Optimal for Three Sum — cannot do better without additional constraints.
- Duplicate skipping: skip identical values after processing each pointer position.
- Early termination: if nums[i] > 0, all remaining sums are positive. Break immediately.
Floyd's Cycle Detection — Slow-Fast Pointers on Linked Lists
The slow-fast pointer pattern extends beyond arrays to linked lists, where it solves cycle detection (Floyd's algorithm), finding the middle node, and detecting the k-th node from the end. The pointers advance by node reference instead of array index, but the logic is identical.
Floyd's cycle detection: slow moves one step, fast moves two steps. If there is a cycle, they will eventually meet inside the cycle. If fast reaches null, there is no cycle. To find the cycle start: after they meet, reset one pointer to the head and advance both one step at a time. They meet at the cycle entrance.
This is O(n) time, O(1) space — no HashSet needed. The space efficiency is the key advantage over the HashSet approach.
package io.thecodeforge.algo; public class FloydsCycleDetection { static class ListNode { int value; ListNode next; ListNode(int val) { this.value = val; } } /** * Detects if a linked list has a cycle using Floyd's algorithm. * Slow pointer: 1 step. Fast pointer: 2 steps. * If they meet, there is a cycle. * O(n) time, O(1) space. */ public static boolean hasCycle(ListNode head) { ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next; // 1 step fast = fast.next.next; // 2 steps if (slow == fast) { // Pointers met — cycle exists return true; } } return false; // Fast reached the end — no cycle } /** * Finds the node where the cycle begins. * After detecting the meeting point, reset one pointer to head. * Advance both one step at a time. They meet at the cycle entrance. */ public static ListNode findCycleStart(ListNode head) { ListNode slow = head; ListNode fast = head; // Phase 1: detect cycle while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; if (slow == fast) break; } if (fast == null || fast.next == null) return null; // No cycle // Phase 2: find cycle entrance slow = head; while (slow != fast) { slow = slow.next; fast = fast.next; } return slow; // This is the cycle entrance } public static void main(String[] args) { // Build: 1 -> 2 -> 3 -> 4 -> 5 -> back to 3 ListNode n1 = new ListNode(1); ListNode n2 = new ListNode(2); ListNode n3 = new ListNode(3); ListNode n4 = new ListNode(4); ListNode n5 = new ListNode(5); n1.next = n2; n2.next = n3; n3.next = n4; n4.next = n5; n5.next = n3; // Cycle: 5 -> 3 System.out.println("Has cycle: " + hasCycle(n1)); ListNode cycleStart = findCycleStart(n1); System.out.println("Cycle starts at node with value: " + (cycleStart != null ? cycleStart.value : "none")); } }
Cycle starts at node with value: 3
- Phase 1: detect cycle. Slow = 1 step, fast = 2 steps. Meet = cycle exists.
- Phase 2: find entrance. Reset slow to head. Both advance 1 step. Meet = entrance.
- Time: O(n). Space: O(1). No extra data structure needed.
- Also used for: finding middle of linked list (fast reaches end, slow is at middle).
- Also used for: finding k-th node from end (advance one pointer k steps first, then both advance together).
| Aspect | Opposite Ends | Slow-Fast (Read-Write) | Sliding Window |
|---|---|---|---|
| Pointer direction | Toward each other | Same direction, different speeds | Same direction, expand/contract |
| Requires sorted input? | Yes (for numeric problems) | No | No |
| Time complexity | O(n) | O(n) | O(n) |
| Space complexity | O(1) | O(1) | O(k) — HashMap or counter |
| Decision rule | Sum comparison → move one pointer | Fast scans, slow writes | Constraint check → shrink or expand |
| Best for | Pair/triplet finding on sorted data | In-place filtering, cycle detection | Substring/subarray optimization |
| Classic problems | Two Sum II, 3Sum, Container With Most Water | Remove Duplicates, Floyd's Cycle | Longest Substring Without Repeating, Min Window Substring |
| Interview frequency | Very high | High | Very high |
| Key edge case | Unsorted input silently fails | Garbage beyond write position | Math.max on left boundary |
🎯 Key Takeaways
- Two pointer technique trades a second loop for a second index variable — same information, O(n) instead of O(n²). The decision rule that moves each pointer is the entire algorithm's intelligence.
- The opposite-ends pattern requires a sorted array because sorting is what makes the 'move left if sum is too small, move right if too large' rule provably correct — without order, there's no safe direction to move.
- The sliding window is two pointers in the same direction with a maintained state (HashMap, counter, running sum) between them. The right pointer always expands; the left pointer only shrinks when a constraint is violated.
- Always protect the sliding window's left boundary with Math.max(windowStart, lastSeen + 1) — this single line is the most commonly missed detail in interviews and causes silent wrong answers rather than crashes.
- Three Sum is Two Sum nested inside a loop. Sort first, fix one element, run two pointers on the remainder. Skip duplicates after each pointer movement.
- Floyd's cycle detection uses slow-fast pointers on linked lists for O(n) time, O(1) space cycle detection. No HashSet needed.
- Each element in a correct two-pointer solution is visited at most twice (once by each pointer). If elements are visited more, the algorithm has a bug.
- Two pointers on unsorted data silently misses valid answers. Always sort first or use a HashMap. Never assume sorted order without verifying.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QGiven an unsorted array of integers and a target sum, can you find a pair that adds up to the target in O(n) time? Walk me through your reasoning — why can't you use the two pointer technique directly here, and what do you use instead?
- QHow would you extend the Two Sum two pointer solution to handle Three Sum — finding all unique triplets that sum to zero — without using more than O(n log n) extra time? What's the trick to avoid duplicate triplets in your output?
- QIn the sliding window approach for 'longest substring with at most K distinct characters', what breaks if you use a HashSet instead of a HashMap to track characters? Can you construct a concrete example that produces a wrong answer with the HashSet version?
- QExplain Floyd's cycle detection algorithm. Why does resetting one pointer to the head after the meeting point find the cycle entrance? Can you prove it mathematically?
- QHow would you solve 'Trapping Rain Water' using two pointers? What is the decision rule that moves each pointer, and why is it correct?
- QGiven a sorted array with duplicates, find the first and last position of a target value in O(log n) time. How does this relate to two pointers?
- QIn the sliding window for 'minimum window substring', why do you need a 'formed' counter instead of just checking the HashMap size? Construct an example where HashMap size alone gives the wrong answer.
- QHow would you find the k-th node from the end of a linked list in one pass using two pointers? What is the initial offset between the two pointers?
- QGiven an array of meeting intervals, how would you use a two-pointer-like approach to find the minimum number of conference rooms needed?
- QExplain why the sliding window pattern is O(n) even though there is a nested while loop inside the for loop. What invariant guarantees that each element is visited at most twice?
Frequently Asked Questions
When should I use two pointers instead of a HashMap?
Use two pointers when the array is sorted and you need O(1) space — it's faster in practice because it avoids hashing overhead. Use a HashMap when the array is unsorted and you can't afford to sort it (e.g. the problem requires returning original indices). HashMap gives O(n) time at the cost of O(n) space.
Does the two pointer technique work on linked lists?
Yes — the slow-fast variant is the canonical approach for linked list cycle detection (Floyd's algorithm), finding the middle node, and detecting the k-th node from the end. The pointers advance by node reference rather than array index, but the logic is identical.
What's the difference between sliding window and the two pointer technique?
Sliding window is a specific application of two pointers where both pointers move in the same direction and the 'window' between them represents a contiguous subarray or substring under a constraint. All sliding window solutions are two pointer solutions, but not all two pointer solutions are sliding windows — for example, opposite-end two pointers for Two Sum is not a sliding window.
When should I use two pointers vs a hash set?
Use two pointers when the array is sorted or when you need O(1) space — two pointers require no extra data structure. Use a hash set when the array is unsorted and you need O(n) time without sorting (O(n log n) extra cost). For example, two-sum on an unsorted array is O(n) with a hash set but requires O(n log n) sorting first if using two pointers.
What is the fast/slow pointer technique?
Also called Floyd's algorithm. A slow pointer moves one step at a time; a fast pointer moves two steps. If they ever meet, there is a cycle (used in linked list cycle detection). If the fast pointer reaches the end, there is no cycle. The slow pointer also finds the middle of a linked list when the fast pointer reaches the end.
Why is the sliding window O(n) even with a nested while loop?
The left pointer (windowStart) only moves forward, never backward. Each element is visited at most twice: once when the right pointer passes it, and once when the left pointer passes it. Total operations: at most 2n, which is O(n). The nested while loop does not create O(n²) because the left pointer's total movement across all iterations is bounded by n.
How do I handle Three Sum duplicates without a Set?
After sorting, skip identical adjacent values at three points: (1) skip duplicate values for the fixed element in the outer loop, (2) skip duplicate values for the left pointer after finding a valid triplet, (3) skip duplicate values for the right pointer after finding a valid triplet. This eliminates duplicates without a HashSet, keeping space at O(1).
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.