DSA sheet · Arrays · Two Pointers pattern

Two Pointers Introduction

This video opens the pattern-wise sheet. The array section has four patterns: two pointers, sliding window, prefix sum and Kadane's algorithm. Two pointers comes first. The teacher doesn't just say "use two pointers". She takes the first problem, Two Sum II, and checks each of the four patterns against it, crossing out the ones that don't fit. Then she writes the brute force (two nested loops, O(n²)), shows from the constraints that it's too slow, and uses the fact that the array is sorted to get an O(n) solution with one pointer at each end.

Why it matters: "sorted array + find a pair" is the most common place two pointers shows up, and the reason it works (each move throws away pairs that can't be the answer) is the same reason it works in 3Sum, Container With Most Water and many others.

Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember

Part 0 · Before starting

What is a "pointer" here?

In these problems a pointer is just a variable that holds an index of the array (0, 1, 2, …). It "points" at one cell. When we say "move the pointer", we mean add 1 to it or subtract 1 from it. "Two pointers" means we keep two such index variables at once and move them by some rule, instead of using two nested loops.

index0123
nums271115left = 0 points at 2, right = 3 points at 15
L  R

The brute force over all pairs, and why it's slow

If a question asks about two elements (a pair), the simple way is to try every pair: an outer loop picks the first index i, an inner loop picks the second index j after it. With n elements that is n·(n−1)/2 pairs, which grows like n². For n = 30,000 that's about 4.5·10⁸ pairs, already past the rough limit of ~10⁸ simple steps per second that a judge allows.

Two pointers is a way of not looking at most of those pairs, while still being sure the answer isn't among the ones we skip. To skip pairs safely we need some extra information about the array. Usually that's the fact that it's sorted (or that we're allowed to sort it). Sorted means each step left gives a smaller-or-equal number, and each step right gives a bigger-or-equal number. That one fact lets us say "every pair using this element is too big" without checking them one by one.

The teacher's 4 array patterns, and how she rules them out

Her habit: before coding, go through the four patterns and ask "does this one fit?" For Two Sum II she does it like this:

patternwhen it's usedfits Two Sum II?
Two pointerswe need two particular positions, often one starting at the first index and one at the last, and we compute something from just those twoyes: we want two numbers, nothing in between them matters
Sliding windowthe question is about a continuous subarray (a "window" with no gaps)no: we don't want a block of neighbours, just two numbers anywhere
Prefix sumwe need the running total of continuous parts, e.g. "sum of everything from index a to index b", stored and reusedno: nothing about sums of continuous parts here
Kadane'smaximum sum of a continuous subarray when negative numbers existno: there are negatives (values go down to −1000), but Kadane's also needs the continuous-sum question, and that isn't asked
Doubt: the constraints allow negative numbers. Doesn't that point to Kadane's?
→ The teacher raises this herself. Negatives alone are not enough. Kadane's is about the best continuous sum, and negatives are just what make that hard. If the question isn't asking about a continuous sum, Kadane's has nothing to do. Always look at what is being asked, not just one feature of the input.

Shape 1: opposite-end pointers (this video)

left starts at index 0, right starts at index n − 1. Each step looks at the two values, then moves one of them inward. They stop when they meet. On a sorted array:

So we have a "make it bigger" button and a "make it smaller" button. If the sum is too big, press "smaller"; if too small, press "bigger". Part B explains carefully why pressing a button never throws away the answer.

The other shapes you'll meet later on this sheet

Not covered in this video. They're here so you can recognise them when they come.

shapehow the pointers moveexample problem
Same direction (slow/fast, or read/write)both start at the left. fast (read) visits every element; slow (write) only moves when we keep something, and marks where the next kept element goesMove Zeroes
Three pointers (low/mid/high)mid scans; things smaller go to the low zone at the front, things bigger go to the high zone at the backSort Colors (Dutch National Flag)
Fix one + two pointersa loop fixes one element; opposite-end pointers search the rest for the other two. Equal values are skipped so no triplet repeats3Sum
Opposite ends + running maxkeep the tallest bar seen from the left and from the right; move the side with the smaller maxTrapping Rain Water
Both ends of a stringcompare the first and last characters, move both inward while they matchValid Palindrome
Expand around a centerstart both pointers at the middle (one char for odd lengths, a gap between two chars for even lengths) and move them outward while the ends matchLongest Palindromic Substring

Why read constraints first?

The teacher reads the constraints before thinking of any approach. They tell us (1) whether a brute force will be fast enough (more than about 10⁸ steps → TLE, "Time Limit Exceeded"), (2) whether values fit in a normal int, and (3) which edge cases exist (can the array be empty? can there be no answer?).


Part A · Two Sum II, brute force: try every pair

LeetCode 167 · Two Sum II – Input Array Is Sorted

1The question in simple words

You get an array numbers that is already sorted in non-decreasing order (each number is ≥ the one before it; equal neighbours are allowed, so "ascending, with possible repeats"). You also get a target. Find the two numbers that add up to target and return their positions.

index0123
numbers271115target 9 → 2 + 7 → answer [1, 2]

2What the constraints tell us

3Intuition: check every pair

Forget patterns for a moment. Point one finger i at a number, and with another finger j try every number after it. If the two add to target, we're done. If not, move j. When j runs out, move i one step and start j again just after it.

4Building the logic

Why j starts at i + 1

We can't use the same element twice, so j must never equal i. Also, pairs before i were already tried when the outer loop was at those earlier positions (pair (0, 2) is the same pair as (2, 0)). So j starts one after i.

Why i stops at the second-last index

i runs while i < n − 1, so its last value is n − 2. If i went all the way to n − 1 (the last cell), then j = i + 1 = n would be past the end, and there's no partner left anyway. Stopping one early keeps j inside the array.

Doubt: in Python, wouldn't range(i + 1, n) just be empty when i = n − 1, with no crash?
→ Yes, Python's range would simply do nothing there. But the idea is the same: the last element has no one after it to pair with, so that pass is useless. Writing range(n - 1) shows you understood why.

The check

For each pair: if numbers[i] + numbers[j] == target, return the positions. Remember the 1-indexing: return [i + 1, j + 1]. The question promises one answer exists, but if we finish both loops without finding it, we return [-1, -1] as a "not found" signal (the teacher does this so every path of the function returns something).

5Approach steps

  1. For i from 0 to n − 2:
  2. For j from i + 1 to n − 1:
  3. If numbers[i] + numbers[j] == target → return [i + 1, j + 1].
  4. After both loops → return [-1, -1].

6Code (Python)

Brute force: O(n²), TLE for n = 3·10⁴
class Solution:
    def twoSum(self, numbers, target):
        n = len(numbers)
        for i in range(n - 1):              # first number: up to the second-last
            for j in range(i + 1, n):       # second number: always after i
                if numbers[i] + numbers[j] == target:
                    return [i + 1, j + 1]   # the question counts from 1
        return [-1, -1]                     # not found (won't happen here)

7Code line by line

linewhat it means
for i in range(n - 1):i takes 0, 1, …, n − 2. The last cell is never the first of a pair, because nothing comes after it.
for j in range(i + 1, n):j takes every index after i. Never equal to i (no using the same element twice), never before i (those pairs were already tried).
if numbers[i] + numbers[j] == target:Does this pair add to target?
return [i + 1, j + 1]Convert from Python's 0-based indexes to the question's 1-based positions.
return [-1, -1]Only reached if no pair worked. The question says that can't happen, but the function should still return something on every path.

8Dry run (hand table)

numbers = [2, 7, 11, 15], target = 9.

stepijvaluessumdecision
1012, 799 == 9 → return [1, 2]

Lucky: the very first pair works. Now a case where it doesn't: target = 26 (11 + 15).

stepijvaluessumdecision
1012, 79≠ 26, next j
2022, 1113≠ 26, next j
3032, 1517≠ 26, j ran out → next i
4127, 1118≠ 26
5137, 1522≠ 26 → next i
62311, 1526== 26 → return [3, 4]

Notice: when i = 0, j moves 1, 2, 3. When i = 1, j moves 2, 3. When i = 2, j moves 3. That's the "for every i, j runs again" pattern that makes it n².

9Complexity & remember

Remember the brute forcei from 0 to n − 2, j from i + 1 to n − 1, check the sum. Correct but O(n²). It doesn't use the "sorted" fact at all, and that's the hint that something better exists.

Part B · Two Sum II, optimal: one pointer at each end

LeetCode 167

1The question again, with the new goal

Same question. Now we want O(n) time, and we'll get it by using the fact that the array is sorted.

2What the constraints tell us now

3Intuition: two buttons, "bigger" and "smaller"

Put left on the smallest number (index 0) and right on the biggest (index n − 1). Add them.

Each step moves exactly one pointer one cell inward, so after at most n − 1 steps they meet.

4Building the logic from the example

Her example: [2, 7, 11, 15], target 9

left on 2, right on 15: 2 + 15 = 17. That's bigger than 9. We want a smaller total. Of 2 and 15, the larger one is 15, so replace 15 with the number just below it: move right to 11.

2 + 11 = 13, still bigger than 9. The teacher asks: should we now move left to 7 instead? That would give 7 + 11 = 18, which goes the wrong way (bigger, not smaller). So no: when the sum is too big, it's always right that moves. Move right to 7.

2 + 7 = 9 = target. Return positions [1, 2].

Why moving a pointer can never skip the answer

This is the heart of the pattern, so let's be careful. At any moment, all the pairs still "alive" are the ones with both indexes between left and right.

So each move throws away a whole row of pairs at once, and we've just proven none of them was the answer. That's how n² pairs become n steps.

Doubt 1: when the sum is too big, could moving left also be OK sometimes?
→ Moving left can only make the sum bigger (or equal), which moves away from the target. Worse, it could throw away the correct left number. In the example, the answer uses 2 at index 0. If we had moved left off 2 when the sum was 13, we would have lost the answer forever.

Why while left < right and not left <= right

The teacher's example: the array has a single 5 and target is 10. If the loop allowed left == right, both pointers could sit on that one 5, add it to itself, get 10, and report a "pair". But that's the same element used twice, which isn't allowed. With left < right, the loop stops the moment they meet, so they're always two different cells.

Doubt 2: but what if the array really has two 5s, like [1, 5, 5, 8] with target 10?
→ Then they're two different cells (index 1 and 2), and left < right still lets us pair them. The rule is about cells, not values.

The 1-indexed answer

The teacher noticed from Example 1 that for 2 and 7 (at indexes 0 and 1) the expected output is [1, 2]. So we return [left + 1, right + 1].

What if the array weren't sorted?

Her closing tip:

5Approach steps

  1. left = 0, right = n − 1.
  2. While left < right: total = numbers[left] + numbers[right].
  3. If total == target → return [left + 1, right + 1].
  4. If total > target → right -= 1 (need smaller).
  5. Else → left += 1 (need bigger).
  6. After the loop → return [-1, -1].

6Code (Python)

Optimal: two pointers from both ends, O(n) time, O(1) space
class Solution:
    def twoSum(self, numbers, target):
        left = 0
        right = len(numbers) - 1
        while left < right:                     # never the same cell
            total = numbers[left] + numbers[right]
            if total == target:
                return [left + 1, right + 1]     # 1-indexed answer
            elif total > target:
                right -= 1                       # too big: take a smaller number
            else:
                left += 1                        # too small: take a bigger number
        return [-1, -1]                          # not found

7Code line by line

linewhat it means
left = 0 right = len(numbers) - 1One pointer on the smallest number, one on the biggest.
while left < right:Both pointers are moving, so we need something to stop them. We stop when they meet, because one cell can't be used twice.
total = numbers[left] + numbers[right]The sum of the current pair.
if total == target: return [left + 1, right + 1]Found the one answer. Add 1 to each index because positions count from 1.
elif total > target: right -= 1Too big. Every pair using right is too big (proof in step 4), so drop it.
else: left += 1Too small. Every pair using left is too small, so drop it.
return [-1, -1]The pointers met without finding a pair. The teacher added this after the editor complained that the function had no return at the end.

8Dry run

numbers = [2, 7, 11, 15], target = 9.

stepleftrightvalues at the pointerssumdecision (which moves, why)pairs still alive
1032, 151717 > 9 → right moves left (15 is too big even with 2)indexes 0..2
2022, 111313 > 9 → right moves left (11 is too big even with 2)indexes 0..1
3012, 799 == 9 → return [1, 2]answer found
step 12711152 + 15 = 17 > 9 → right--
L  R
step 22711152 + 11 = 13 > 9 → right-- (15 is out for good)
L R 
step 32711152 + 7 = 9 → [1, 2]
LR  

A second run where left has to move: numbers = [−3, 1, 4, 6, 9], target = 7.

stepleftrightvaluessumdecision
104−3, 966 < 7 → left moves right (−3 is too small even with 9)
2141, 91010 > 7 → right moves left
3131, 67== 7 → return [2, 4]

9Complexity & remember

She submitted it and it beat about 96% of solutions.

Remember the optimalleft at the start, right at the end, while left < right. Too big → right -= 1. Too small → left += 1. Equal → return [left + 1, right + 1]. Only works because the array is sorted.

Part C · Revision page

Brute forceTwo pointers
ideatry every pair i < jstart at both ends, move the pointer that fixes the sum
uses "sorted"?noyes, that's what makes each move safe
looptwo nested for loopsone while loop, left < right
time / spaceO(n²) / O(1), TLE for n = 3·10⁴O(n) / O(1)
patternuse it when
Two pointerstwo particular positions (often both ends), usually on a sorted array
Sliding windowa continuous subarray / window
Prefix sumrepeated sums of continuous ranges
Kadane'sbest continuous sum with negative numbers
Not sorted? Sort first if allowed, else use a hash map.
If you remember only 5 lines 1. Sorted array + find a pair → two pointers at the two ends.
2. Too big → move right left. Too small → move left right.
3. A move is safe because every pair using the dropped element was already too big (or too small).
4. Loop while left < right: the same cell can't be used twice.
5. This problem's answer is 1-indexed: [left + 1, right + 1].
Mistakes to avoid ✗ left <= right (adds an element to itself)
✗ moving left when the sum is too big (goes the wrong way and can lose the answer)
✗ returning 0-based indexes
✗ using this on an unsorted array without sorting first
✗ picking Kadane's just because negatives exist
test it yourself (paste under either Solution above)
s = Solution()
print(s.twoSum([2, 7, 11, 15], 9))      # [1, 2]
print(s.twoSum([2, 3, 4], 6))           # [1, 3]
print(s.twoSum([-1, 0], -1))            # [1, 2]
print(s.twoSum([1, 5, 5, 8], 10))       # [2, 3]  (two different 5s)
print(s.twoSum([-3, 1, 4, 6, 9], 7))    # [2, 4]

Based on this video: Two Pointers Introduction | Two Sum II