← All Guides

Arrays & Strings

9 drills · 9 pattern exercises · 19 micro drills

Patterns Overview

Patterns used in this topic, with difficulty breakdown.

Two Pointers 4 drills 4 Easy
Slicing 1 drill 1 Easy
Running Max 1 drill 1 Med
Sliding Window 1 drill 1 Easy
Prefix Sum 1 drill 1 Easy
Prefix/Suffix 1 drill 1 Med

Foundation Drill

1 Reverse array in-place Easy Two Pointers

You're given a list of numbers and need to flip the order without creating a new list.

Start here in Foundations →

Coding Drills

All 9 drills grouped by pattern. Each includes prompt, solution, and complexity.

Two Pointers (4)

1 Reverse array in-place Easy

In plain English: You're given a list of numbers and need to flip the order without creating a new list.

Two pointers converging from opposite ends is the go-to for symmetric array operations — each swap brings both ends closer to the center.

Prompt

Given an array a, reverse it in-place.

Solution

def reverse(a):
    l, r = 0, len(a) - 1
    # converge from both ends
    while l < r:
        a[l], a[r] = a[r], a[l]
        l += 1
        r -= 1
    return a

# Test: reverse([1,2,3,4,5]) == [5,4,3,2,1]
O(n) time · O(1) space
4 Two sum (sorted array) Easy

In plain English: In a sorted list, find two numbers that add up to a specific target.

In a sorted array, the sum of two pointers tells you which direction to move — too small means the left should increase, too big means the right should decrease.

Prompt

Given sorted array a and target t, return indices of the pair that sums to t.

Solution

def two_sum_sorted(a, t):
    l, r = 0, len(a) - 1
    while l < r:
        s = a[l] + a[r]
        # sum tells us which pointer to move
        if s == t:
            return [l, r]
        elif s < t:
            l += 1
        else:
            r -= 1
    return []

# Test: two_sum_sorted([1,2,3,5,8], 10) == [2, 4]
O(n) time · O(1) space
5 Check palindrome string Easy

In plain English: Check whether a word or phrase reads the same forwards and backwards.

A palindrome is symmetric, so comparing from both ends toward the center catches any mismatch in O(n/2) comparisons.

Prompt

Given string s, return True if it's a palindrome.

Solution

def is_palindrome(s):
    l, r = 0, len(s) - 1
    while l < r:
        if s[l] != s[r]:  # mismatch = not palindrome
            return False
        l += 1; r -= 1
    return True

# Test: is_palindrome("racecar") == True
O(n) time · O(1) space
6 Merge two sorted arrays Easy

In plain English: Combine two already-sorted lists into a single sorted list.

Both arrays are sorted, so the smallest unprocessed element is always at one of the two front pointers — just pick the smaller one.

Prompt

Given sorted arrays a and b, return a single merged sorted array.

Solution

def merge(a, b):
    res, i, j = [], 0, 0
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:  # always pick the smaller front
            res.append(a[i]); i += 1
        else:
            res.append(b[j]); j += 1
    res.extend(a[i:])
    res.extend(b[j:])
    return res

# Test: merge([1,3,5], [2,4,6]) == [1,2,3,4,5,6]
O(n+m) time · O(n+m) space

Slicing (1)

2 Left rotate array by k Easy

In plain English: Shift every element k positions to the left, wrapping elements from the front to the back.

The 3-reverse trick works because reversing segments then the whole array is equivalent to a cyclic shift — no extra space needed.

Prompt

Given array a and integer k, return the left-rotated array.

Solution

def left_rotate(a, k):
    k = k % len(a)  # handle wrap-around
    return a[k:] + a[:k]

# Test: left_rotate([1,2,3,4,5], 2) == [3,4,5,1,2]

# In-place variant (3 reverses):
def left_rotate_inplace(a, k):
    k = k % len(a)
    def rev(lo, hi):
        while lo < hi:
            a[lo], a[hi] = a[hi], a[lo]
            lo += 1; hi -= 1
    rev(0, k - 1)
    rev(k, len(a) - 1)
    rev(0, len(a) - 1)
O(n) time · O(1) space (in-place)

Running Max (1)

3 Max subarray sum (Kadane's) Medium

In plain English: Find the consecutive chunk of numbers in an array that adds up to the largest possible sum.

At each position, decide: extend the current subarray or start fresh. If the running sum goes negative, starting over is always better.

Prompt

Given array a, find the contiguous subarray with the largest sum.

Solution

def max_subarray(a):
    cur_max = global_max = a[0]
    for x in a[1:]:
        cur_max = max(x, cur_max + x)  # extend or restart
        global_max = max(global_max, cur_max)
    return global_max

# Test: max_subarray([-2,1,-3,4,-1,2,1,-5,4]) == 6
O(n) time · O(1) space

Sliding Window (1)

7 Sliding window max sum of k elements Easy

In plain English: Find which group of k consecutive elements in an array has the biggest total.

Instead of recalculating the sum from scratch, subtract the leaving element and add the entering one — O(1) per slide.

Prompt

Given array a and window size k, find the maximum sum of any contiguous k elements.

Solution

def max_sum_k(a, k):
    window = sum(a[:k])
    best = window
    for i in range(k, len(a)):
        window += a[i] - a[i - k]  # slide: add right, drop left
        best = max(best, window)
    return best

# Test: max_sum_k([1,4,2,10,2,3,1,0,20], 4) == 24
O(n) time · O(1) space

Prefix Sum (1)

8 Range sum query (prefix sum) Easy

In plain English: Pre-process an array so you can instantly answer 'what is the sum between index l and r?'

Prefix sums turn any range-sum query into a subtraction of two precomputed values — trade O(n) setup for O(1) per query.

Prompt

Given array a, answer multiple range-sum queries [l, r] efficiently.

Solution

def build_prefix(a):
    p = [0] * (len(a) + 1)
    for i in range(len(a)):
        p[i + 1] = p[i] + a[i]  # p[i] = sum of a[0..i-1]
    return p

def range_sum(p, l, r):
    return p[r + 1] - p[l]

# a = [1,3,5,7,9]; p = build_prefix(a)
# range_sum(p, 1, 3) == 15  (3+5+7)
O(n) build · O(1) per query

Prefix/Suffix (1)

133 Product of Array Except Self Medium

In plain English: For each number in the array, compute the product of all the other numbers without using division.

Build prefix products left-to-right, then suffix products right-to-left. result[i] = prefix[i] * suffix[i]. Can be done in one array with two passes to stay O(1) extra space.

Prompt

Given an integer array nums, return an array where each element is the product of all other elements. Solve without using division and in O(n).

Solution

def product_except_self(nums):
    n = len(nums)
    result = [1] * n
    # left pass: prefix products
    prefix = 1
    for i in range(n):
        result[i] = prefix
        prefix *= nums[i]
    # right pass: suffix products
    suffix = 1
    for i in range(n - 1, -1, -1):
        result[i] *= suffix
        suffix *= nums[i]
    return result

# Test: product_except_self([1,2,3,4]) == [24,12,8,6]
# Test: product_except_self([-1,1,0,-3,3]) == [0,0,9,0,0]
O(n) time · O(1) space (output array not counted)

Pattern Recognition

9 exercises: spot the signals, pick the method, avoid the trap.

1 Reverse array in-place Easy

Given an array a, reverse it in-place.

Signals

Two Pointers

Swap elements at left and right pointers, converge toward center. O(n) time, O(1) space.

Stack

Push all → pop all gives reversed order, but uses O(n) space — violates in-place requirement.

Trap

Don't reach for slicing (a[::-1]) in an interview — it creates a new array, not in-place.

2 Left rotate array by k Easy

Given array a and integer k, return the left-rotated array.

Signals

Two Pointers

3-reverse trick: reverse [0:k], reverse [k:n], reverse all. O(n) time, O(1) space.

Sliding Window

Slicing a[k:] + a[:k] is clean but creates a new array — fine if in-place isn't required.

Trap

Naive shift-one-at-a-time loop is O(n*k) — way too slow for large k.

3 Max subarray sum (Kadane's) Medium

Given array a, find the contiguous subarray with the largest sum.

Signals

Dynamic Programming

Kadane's: dp[i] = max(a[i], dp[i-1]+a[i]). Decide at each element: extend or restart. O(n).

Divide & Conquer

Split array, solve halves, check cross-boundary max. O(n log n) — correct but slower.

Trap

Brute force checking all subarrays is O(n²) or O(n³). The 'extend or restart' insight is key.

4 Two sum (sorted array) Easy

Given sorted array a and target t, return indices of the pair that sums to t.

Signals

Two Pointers

Start from both ends. If sum < target, move left up; if sum > target, move right down. O(n).

Hash Map

Works (O(n)) but wastes the sorted property. Interviewer wants you to use the sort.

Trap

Binary search for each element is O(n log n) — works but two pointers is cleaner and O(n).

5 Check palindrome string Easy

Given string s, return True if it's a palindrome.

Signals

Two Pointers

Compare s[l] and s[r], converge inward. First mismatch → not a palindrome. O(n) time, O(1) space.

Stack

Push first half, compare with second half popping. Same complexity but more code.

Trap

s == s[::-1] works in Python but uses O(n) extra space and doesn't show algorithmic thinking.

6 Merge two sorted arrays Easy

Given sorted arrays a and b, return a single merged sorted array.

Signals

Two Pointers

One pointer per array. Always pick the smaller front element. O(n+m) time.

Sorting

Concatenate and sort: O((n+m) log(n+m)). Works but ignores that inputs are already sorted.

Trap

Don't re-sort — the merge step is the whole point. Interviewers test if you exploit sorted order.

7 Sliding window max sum of k elements Easy

Given array a and window size k, find the maximum sum of any contiguous k elements.

Signals

Sliding Window

Compute first window sum. Slide: subtract a[i-k], add a[i]. O(n) time, O(1) space.

Prefix Sum

prefix[i+k] - prefix[i] for each window. Same O(n) but uses O(n) extra space.

Trap

Recomputing sum from scratch for each window is O(n*k). The slide trick makes it O(n).

8 Range sum query (prefix sum) Easy

Given array a, answer multiple range-sum queries [l, r] efficiently.

Signals

Prefix Sum

Build prefix array in O(n). Each query: prefix[r+1] - prefix[l] in O(1).

Sliding Window

Only works for consecutive queries of same length. Prefix sum handles arbitrary ranges.

Trap

Summing the range each time is O(n) per query → O(n*q) total. Prefix sum makes it O(n + q).

133 Product of Array Except Self Medium

Given array nums, return an array where each element is the product of all other elements. No division allowed.

Signals

Prefix Sum

Build left product array, then multiply in right products in reverse. O(n) time, O(1) extra space.

Two Pointers

Left pointer accumulates prefix, right pointer accumulates suffix simultaneously.

Trap

Don't use division (fails on zeros). The prefix/suffix approach handles zeros naturally.

Related Micro Drills

19 quick recall drills to reinforce this topic.

126 Remove duplicates in-place Pointer Manipulation 10s

Write-pointer compaction for sorted arrays. Returns new length.

w = 1
for i in range(1, len(a)):
    if a[i] != a[i-1]:
        a[w] = a[i]
        w += 1
return w
86 Remove element in-place Pointer Manipulation 10s

Write-pointer skips unwanted values. O(1) space removal pattern.

w = 0
for r in range(len(a)):
    if a[r] != val:
        a[w] = a[r]
        w += 1
# a[:w] has val removed
116 Rotate matrix 90° CW Interval & Math 15s

Transpose + reverse-rows rotates 90 degrees clockwise in-place. Classic matrix manipulation.

n = len(m)
for i in range(n):
    for j in range(i+1, n):
        m[i][j], m[j][i] = m[j][i], m[i][j]
for row in m:
    row.reverse()
48 Left boundary (bisect_left) Search 15s

Left boundary search finds the first occurrence. Key for range queries and count problems.

lo, hi = 0, len(a)
while lo &lt; hi:
    mid = lo + (hi - lo) // 2
    if a[mid] &lt; target: lo = mid + 1
    else: hi = mid
return lo
4 Rotate right by 1 Pointer Manipulation 10s

Array rotation underpins circular buffer logic and modular index arithmetic.

a[-1:] + a[:-1]
# or
a.insert(0, a.pop())
101 Sliding window max sum (k) Sliding Window 10s

Fixed-size sliding window computes running aggregates in O(n). Core optimization pattern.

mx = cur = sum(a[:k])
for i in range(k, len(a)):
    cur += a[i] - a[i-k]
    mx = max(mx, cur)
169 Kadane's Algorithm Recursion & DP 10s

At each element: extend current subarray or start fresh. The simplest DP pattern — must be instant.

def maxSubArray(nums):
    cur = res = nums[0]
    for x in nums[1:]:
        cur = max(x, cur + x)
        res = max(res, cur)
    return res
211 Max path sum (any-to-any) Tree Traversal 15s

Bottom-up DFS. At each node: update global max with left+node+right. Return single branch for parent to use.

def maxPathSum(root):
    res = [float('-inf')]
    def dfs(node):
        if not node: return 0
        left = max(dfs(node.left), 0)   # ignore negative paths
        right = max(dfs(node.right), 0)
        # path through this node as turning point
        res[0] = max(res[0], left + node.val + right)
        # return max single-side path for parent
        return node.val + max(left, right)
    dfs(root)
    return res[0]
76 Merge two sorted lists Divide & Conquer 15s

Merging sorted lists is the core of merge sort and merge-k-lists.

i = j = 0
merged = []
while i &lt; len(a) and j &lt; len(b):
    if a[i] &lt;= b[j]:
        merged.append(a[i]); i += 1
    else:
        merged.append(b[j]); j += 1
merged.extend(a[i:])
merged.extend(b[j:])
8 Remove duplicates (sorted array) Pointer Manipulation 10s

Two-pointer write keeps unique elements in-place with O(1) space. Core array cleanup technique.

# Two-pointer write — keep unique in-place
w = 1
for r in range(1, len(a)):
    if a[r] != a[r - 1]:
        a[w] = a[r]
        w += 1
# a[:w] has unique elements
84 Converging two pointers Pointer Manipulation 10s

Converging pointers shrink the search space from both ends. Core of two-sum-sorted and palindrome.

l, r = 0, len(a) - 1
while l &lt; r:
    # process a[l], a[r]
    l += 1; r -= 1
5 Check if sorted Pointer Manipulation 10s

Sortedness check is a precondition guard for binary search and merge operations.

all(a[i] &lt;= a[i+1] for i in range(len(a)-1))
259 Check overlapping intervals Interval & Math 10s

Sort by start, then check if each interval starts before the previous one ends. Foundation for meeting rooms.

intervals.sort()
for i in range(1, len(intervals)):
    if intervals[i][0] &lt; intervals[i-1][1]:
        return True  # overlap
return False
106 Minimum window substring check Sliding Window 10s

Tracking have/need counts is the matching logic inside minimum-window-substring.

need, have = len(cnt_t), 0
for c in window:
    window_cnt[c] += 1
    if window_cnt[c] == cnt_t[c]:
        have += 1
113 Merge intervals Interval & Math 15s

Sort-then-merge is the standard O(n log n) approach to interval union.

a.sort()
res = [a[0]]
for s, e in a[1:]:
    if s &lt;= res[-1][1]:
        res[-1][1] = max(res[-1][1], e)
    else:
        res.append([s, e])
89 Fixed window sum Pointer Manipulation 10s

Fixed-window sliding avoids re-summing by adding the new element and removing the old one.

window = sum(a[:k])
best = window
for i in range(k, len(a)):
    window += a[i] - a[i - k]
    best = max(best, window)
87 Move zeros to end Pointer Manipulation 10s

Move-zeroes is remove-element followed by a fill. Classic two-pointer warm-up.

w = 0
for r in range(len(a)):
    if a[r] != 0:
        a[w] = a[r]
        w += 1
while w &lt; len(a):
    a[w] = 0
    w += 1
270 Prefix sum build and query Pointer Manipulation 10s

Prefix sum gives O(1) range queries after O(n) build. p[r+1] - p[l] = sum(a[l..r]). Off-by-one: p has length n+1.

p = [0] * (len(a) + 1)
for i in range(len(a)):
    p[i+1] = p[i] + a[i]
# range sum [l, r] inclusive:
total = p[r+1] - p[l]
133 Product except self (no div) Pointer Manipulation 15s

Prefix/suffix product arrays avoid division. O(n) time, O(1) extra space with in-place right pass.

n = len(a)
res = [1] * n
for i in range(1, n): res[i] = res[i-1] * a[i-1]
right = 1
for i in range(n-2, -1, -1):
    right *= a[i+1]
    res[i] *= right