Skip to Content
Course content

447: Solving Array and String Problems

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a colleague who was building a feature to detect "bursts" of activity in a server log. He had written a nested loop that scanned a list of timestamps to find the longest sequence where events happened within 5 seconds of each other. On his local machine with a few hundred test logs, it was instantaneous. But once we deployed it to a production environment with millions of entries, the process hung, the CPU spiked, and the monitoring alerts started screaming. He had accidentally built an $O(n^2)$ algorithm for a problem that only required $O(n)$. That's the danger of array and string problems; the "obvious" solution is often the one that crashes your server.

The Power of Two Pointers

When you're staring at a linear data structure and your instinct is to write a loop inside a loop, stop. Most of the time, you can replace that inner loop by using two pointers moving at different speeds or from different directions. I use this constantly when dealing with sorted arrays. Instead of checking every possible pair, you place one pointer at the start and one at the end, moving them inward based on a condition.

# Example: Finding if two numbers in a sorted list sum to a target
def has_target_sum(nums, target):
    left, right = 0, len(nums) - 1
    while left < right:
        current_sum = nums[left] + nums[right]
        if current_sum == target:
            return True
        elif current_sum < target:
            left += 1  # Need a larger sum, move the left pointer up
        else:
            right -= 1 # Need a smaller sum, move the right pointer down
    return False

It's a simple shift in thinking, but it turns a potentially sluggish operation into a lightning-fast one. You aren't blindly searching; you're strategically narrowing the search space.

Sliding Windows for Contiguous Sequences

The "two-pointer" concept evolves into a sliding window when you're dealing with sub-segments of a string or array. Think of it like a camera lens that expands and contracts as it moves across the data. This is exactly how you'd solve that server log problem my colleague struggled with. Instead of re-scanning the list for every single element, you maintain a window that represents your current valid sequence.

If the window no longer meets your criteria (say, the time gap becomes too large), you don't restart the whole process. You simply slide the left boundary of the window forward until the criteria are met again. You're effectively processing each element exactly twice—once when the right edge hits it, and once when the left edge passes it.

Mapping Frequencies to Avoid Re-scanning

Strings are just arrays of characters, but they bring their own set of headaches, especially when you need to track occurrences. I see developers spend way too much time using `.count()` inside a loop, which is a silent performance killer because `.count()` is itself a loop. Instead, use a dictionary or collections.Counter to create a frequency map in a single pass.

from collections import Counter

def find_first_non_repeating(s):
    # One pass to build the map: O(n)
    counts = Counter(s)
    # Second pass to find the first char with count 1: O(n)
    for char in s:
        if counts[char] == 1:
            return char
    return None

By trading a little bit of memory (the dictionary) for speed, you avoid the $O(n^2)$ trap. In the professional world, we almost always trade memory for time, because RAM is cheap but user patience is not.




📋 Practical Task

The Unique Substring Length Validator

You are building a tool to analyze DNA sequences (represented as strings). You need to find the length of the longest substring that contains no repeating characters. For example, in the sequence "abcabcbb", the longest unique substring is "abc" with a length of 3. In "bbbbb", it is "b" with a length of 1.

Implement a function longest_unique_sequence(sequence) that uses a sliding window approach to solve this in $O(n)$ time. Avoid using nested loops that re-scan the string from the beginning.

def longest_unique_sequence(sequence):
    # Your code here
    pass

# Test cases to verify your logic:
print(longest_unique_sequence("abcabcbb")) # Expected: 3
print(longest_unique_sequence("pwwkew"))    # Expected: 3
print(longest_unique_sequence("abcdef"))    # Expected: 6
print(longest_unique_sequence(""))          # Expected: 0
Rating
0 0

There are no comments for now.

to be the first to leave a comment.