DSA Material

Arrays

Share to

Arrays are contiguous blocks of memory. In Python, list behaves like a dynamic array: random access is fast, and append is usually fast.

Book alignment (Chapter 3: Arrays and Vectors)

From the Necaise book, this page maps to:

The key design point is to separate:

That separation explains why appends are amortized O(1).

Array operation overview

Mental model

Keep these ideas clear:

When to use arrays

When not to use arrays

Operations

get(index)

Array memory is contiguous, so the element address is computed directly from index. No traversal is needed.

Array get operation

def get(self, index):
    if index < 0 or index >= len(self.data):
        raise IndexError("index out of range")
    return self.data[index]

append(value)

append places the value at the end. Most appends are constant time. Rarely, the backing storage grows and copies data, which gives amortized O(1).

Array append operation

def append(self, value):
    self.data.append(value)

insert(index, value)

Elements from index to the end shift right by one slot, then the new value is placed at index. Cost is linear in the number of shifted elements.

Array insert operation

def insert(self, index, value):
    if index < 0 or index > len(self.data):
        raise IndexError("index out of range")
    self.data.insert(index, value)

delete(index)

Element at index is removed, and all later elements shift left by one slot.

Array delete operation

def delete(self, index):
    if index < 0 or index >= len(self.data):
        raise IndexError("index out of range")
    return self.data.pop(index)

Full implementation

class DynamicArray:
    def __init__(self):
        self.data = []

    def append(self, value):
        self.data.append(value)

    def get(self, index):
        if index < 0 or index >= len(self.data):
            raise IndexError("index out of range")
        return self.data[index]

    def set(self, index, value):
        if index < 0 or index >= len(self.data):
            raise IndexError("index out of range")
        self.data[index] = value

    def insert(self, index, value):
        if index < 0 or index > len(self.data):
            raise IndexError("index out of range")
        self.data.insert(index, value)

    def delete(self, index):
        if index < 0 or index >= len(self.data):
            raise IndexError("index out of range")
        return self.data.pop(index)

    def __len__(self):
        return len(self.data)

    def __repr__(self):
        return f"DynamicArray({self.data})"

Complexity summary

Operation Time Why
Read by index O(1) Direct address calculation
Update by index O(1) Overwrite existing slot
Append O(1) amortized Occasional resize and copy
Insert in middle O(n) Shift right
Delete in middle O(n) Shift left
Search unsorted array O(n) Sequential scan

Common patterns

Two pointers

Useful for sorted arrays and palindrome checks. One pointer starts at the beginning and the other at the end; they move toward each other.

def is_palindrome(s):
    left, right = 0, len(s) - 1
    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    return True

Sliding window

Track a running window of size k to avoid repeated summing. Shift the window one step at a time.

def max_sum_k(nums, k):
    if k > len(nums):
        return None

    window_sum = sum(nums[:k])
    best = window_sum

    for i in range(k, len(nums)):
        window_sum += nums[i] - nums[i - k]
        best = max(best, window_sum)

    return best

Frequency counting

Count occurrences of each element. Useful for anagram checks, duplicate detection, and histogram problems.

def is_anagram(a, b):
    if len(a) != len(b):
        return False

    count = {}
    for ch in a:
        count[ch] = count.get(ch, 0) + 1

    for ch in b:
        if ch not in count:
            return False
        count[ch] -= 1
        if count[ch] < 0:
            return False

    return True

Prefix sums

Precompute cumulative sums to answer range-sum queries in O(1) after O(n) setup.

def build_prefix(nums):
    prefix = [0] * (len(nums) + 1)
    for i in range(len(nums)):
        prefix[i + 1] = prefix[i] + nums[i]
    return prefix


def range_sum(prefix, left, right):
    return prefix[right + 1] - prefix[left]

Edge-case checklist

Typical mistakes