BeginnerDSA ยท Lesson 2

Arrays and Strings

Master arrays and string manipulation patterns: two-pointer, sliding window, and prefix sums

Arrays

An array stores elements in contiguous memory, allowing O(1) access by index.

# Python list as array
arr = [10, 20, 30, 40, 50]

# Access: O(1)
print(arr[0])     # 10
print(arr[-1])    # 50
print(arr[2])     # 30

# Modification: O(1)
arr[2] = 99
print(arr)        # [10, 20, 99, 40, 50]

# Append to end: O(1) amortized
arr.append(60)

# Insert in middle: O(n) โ€” shifts elements
arr.insert(2, 25)

# Delete by index: O(n) โ€” shifts elements
arr.pop(2)

Two-Pointer Technique

Use two pointers to reduce O(nยฒ) to O(n).

Two Sum (Sorted Array)

def two_sum_sorted(arr, target):
    """Find two numbers that sum to target in sorted array"""
    left, right = 0, len(arr) - 1
    while left < right:
        total = arr[left] + arr[right]
        if total == target:
            return left, right
        elif total < target:
            left += 1
        else:
            right -= 1
    return None

print(two_sum_sorted([1, 3, 5, 7, 9], 12))  # (2, 4) โ†’ 5+7=12

Remove Duplicates (In-Place)

def remove_duplicates(arr):
    """Remove duplicates from sorted array in-place, return new length"""
    if not arr:
        return 0
    slow = 0
    for fast in range(1, len(arr)):
        if arr[fast] != arr[slow]:
            slow += 1
            arr[slow] = arr[fast]
    return slow + 1

arr = [1, 1, 2, 3, 3, 4]
length = remove_duplicates(arr)
print(arr[:length])  # [1, 2, 3, 4]

Valid Palindrome

def is_palindrome(s):
    """Check if string is a palindrome, ignoring non-alphanumeric chars"""
    left, right = 0, len(s) - 1
    while left < right:
        while left < right and not s[left].isalnum():
            left += 1
        while left < right and not s[right].isalnum():
            right -= 1
        if s[left].lower() != s[right].lower():
            return False
        left += 1
        right -= 1
    return True

print(is_palindrome("A man, a plan, a canal: Panama"))  # True

Sliding Window Technique

Efficiently process subarrays/substrings of a given size.

Maximum Sum Subarray of Size K

def max_sum_subarray(arr, k):
    """Fixed window: find max sum of any k consecutive elements"""
    n = len(arr)
    if n < k:
        return None
    # Initial window
    window_sum = sum(arr[:k])
    max_sum = window_sum
    # Slide window: add right element, remove left element
    for i in range(k, n):
        window_sum += arr[i] - arr[i - k]
        max_sum = max(max_sum, window_sum)
    return max_sum

print(max_sum_subarray([2, 1, 5, 1, 3, 2], 3))  # 9 (5+1+3)

Longest Substring Without Repeating Characters

def length_of_longest_substring(s):
    """Variable window: expand right, shrink left on duplicate"""
    char_index = {}
    max_len = 0
    left = 0
    for right, char in enumerate(s):
        if char in char_index and char_index[char] >= left:
            left = char_index[char] + 1
        char_index[char] = right
        max_len = max(max_len, right - left + 1)
    return max_len

print(length_of_longest_substring("abcabcbb"))  # 3 (abc)
print(length_of_longest_substring("pwwkew"))    # 3 (wke)

Minimum Size Subarray Sum

def min_subarray_len(target, arr):
    """Find minimum length subarray with sum >= target"""
    left = 0
    window_sum = 0
    min_len = float('inf')
    for right in range(len(arr)):
        window_sum += arr[right]
        while window_sum >= target:
            min_len = min(min_len, right - left + 1)
            window_sum -= arr[left]
            left += 1
    return min_len if min_len != float('inf') else 0

print(min_subarray_len(7, [2, 3, 1, 2, 4, 3]))  # 2 (4+3)

Prefix Sums

Precompute cumulative sums for O(1) range queries.

def build_prefix(arr):
    prefix = [0] * (len(arr) + 1)
    for i, val in enumerate(arr):
        prefix[i + 1] = prefix[i] + val
    return prefix

def range_sum(prefix, left, right):
    """Sum of arr[left:right+1] in O(1)"""
    return prefix[right + 1] - prefix[left]

arr = [1, 2, 3, 4, 5]
prefix = build_prefix(arr)
print(range_sum(prefix, 1, 3))   # 9 (2+3+4)
print(range_sum(prefix, 0, 4))   # 15 (sum of all)

# 2D prefix sum for matrix:
def build_2d_prefix(matrix):
    rows, cols = len(matrix), len(matrix[0])
    P = [[0] * (cols + 1) for _ in range(rows + 1)]
    for r in range(rows):
        for c in range(cols):
            P[r+1][c+1] = (P[r][c+1] + P[r+1][c]
                          - P[r][c] + matrix[r][c])
    return P

def region_sum(P, r1, c1, r2, c2):
    return P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1]

Exercises

Exercise 1: Container With Most Water

Given heights, find two lines that form a container holding the most water.

Solution:

def max_water(heights):
    left, right = 0, len(heights) - 1
    max_vol = 0
    while left < right:
        width = right - left
        height = min(heights[left], heights[right])
        max_vol = max(max_vol, width * height)
        if heights[left] < heights[right]:
            left += 1
        else:
            right -= 1
    return max_vol

print(max_water([1, 8, 6, 2, 5, 4, 8, 3, 7]))  # 49

Exercise 2: Subarray Sum Equals K

Count subarrays that sum to exactly k.

Solution:

def subarray_sum(nums, k):
    count = 0
    prefix_sum = 0
    seen = {0: 1}  # prefix_sum: count
    for num in nums:
        prefix_sum += num
        # If (prefix_sum - k) was seen, there's a subarray summing to k
        count += seen.get(prefix_sum - k, 0)
        seen[prefix_sum] = seen.get(prefix_sum, 0) + 1
    return count

print(subarray_sum([1, 1, 1], 2))    # 2
print(subarray_sum([1, 2, 3], 3))    # 2

Exercise 3: Sliding Window Maximum

Find the maximum value in each window of size k.

Solution:

from collections import deque

def sliding_window_max(arr, k):
    """Uses monotonic deque for O(n) solution"""
    dq = deque()  # stores indices, decreasing values
    result = []
    for i, val in enumerate(arr):
        # Remove indices outside window
        while dq and dq[0] < i - k + 1:
            dq.popleft()
        # Remove smaller elements from back
        while dq and arr[dq[-1]] < val:
            dq.pop()
        dq.append(i)
        if i >= k - 1:
            result.append(arr[dq[0]])
    return result

print(sliding_window_max([1, 3, -1, -3, 5, 3, 6, 7], 3))
# [3, 3, 5, 5, 6, 7]