Steven's Knowledge

Arrays & Strings

The most fundamental data structures — manipulation patterns, prefix sums, and Kadane's algorithm

Arrays & Strings

Arrays and strings are where most interview problems live. Not because they are complex data structures, but because they are the canvas for demonstrating algorithmic thinking. You need to be fluent with the manipulation patterns that recur across hundreds of problems.

Core Operations and Their Costs

OperationArrayString (immutable)
Access by indexO(1)O(1)
SearchO(n)O(n)
Insert at endO(1) amortizedO(n) -- creates new string
Insert at positionO(n)O(n)
DeleteO(n)O(n)
ConcatenationO(n)O(n) -- watch out for loops

The string trap: In most languages, strings are immutable. Concatenating in a loop creates a new string each time, turning O(n) into O(n^2). Use an array/builder and join at the end.

// BAD: O(n^2) due to string concatenation in loop
function buildStringBad(n: number): string {
  let result = '';
  for (let i = 0; i < n; i++) {
    result += String(i);  // new string each time
  }
  return result;
}

// GOOD: O(n)
function buildStringGood(n: number): string {
  const parts: string[] = [];
  for (let i = 0; i < n; i++) {
    parts.push(String(i));
  }
  return parts.join('');
}

Prefix Sum

Prefix sum lets you answer "what is the sum of elements from index i to j?" in O(1) after O(n) preprocessing.

function buildPrefixSum(arr: number[]): number[] {
  const prefix = new Array(arr.length + 1).fill(0);
  for (let i = 0; i < arr.length; i++) {
    prefix[i + 1] = prefix[i] + arr[i];
  }
  return prefix;
}

// Sum of elements from index i to j (inclusive)
function rangeSum(prefix: number[], i: number, j: number): number {
  return prefix[j + 1] - prefix[i];
}

// Example: arr = [1, 2, 3, 4, 5]
// prefix = [0, 1, 3, 6, 10, 15]
// rangeSum(1, 3) = prefix[4] - prefix[1] = 10 - 1 = 9  (2+3+4)

When to use prefix sum:

  • Subarray sum queries after preprocessing
  • Finding subarrays with a target sum (combine with hash map)
  • 2D prefix sums for matrix region queries

Subarray Sum Equals K

A classic that combines prefix sum with hash map:

function subarraySum(nums: number[], k: number): number {
  const prefixCount = new Map<number, number>();
  prefixCount.set(0, 1);
  let sum = 0;
  let count = 0;

  for (const num of nums) {
    sum += num;
    // If (sum - k) was a prefix sum before, then the subarray
    // between that prefix and current index sums to k
    count += prefixCount.get(sum - k) ?? 0;
    prefixCount.set(sum, (prefixCount.get(sum) ?? 0) + 1);
  }

  return count;
}

Kadane's Algorithm

Find the maximum sum contiguous subarray in O(n). The idea: at each position, either extend the current subarray or start a new one.

function maxSubarraySum(nums: number[]): number {
  let maxSum = nums[0];
  let currentSum = nums[0];

  for (let i = 1; i < nums.length; i++) {
    // Either extend the previous subarray or start fresh
    currentSum = Math.max(nums[i], currentSum + nums[i]);
    maxSum = Math.max(maxSum, currentSum);
  }

  return maxSum;
}

// Example: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
// Answer: 6 (subarray [4, -1, 2, 1])

Variations:

  • Maximum product subarray: track both max and min (negative * negative = positive)
  • Maximum circular subarray: max(kadane(arr), totalSum - kadane_min(arr))
  • Return the actual subarray: track start/end indices

In-Place Array Manipulation

Many interview problems ask you to modify an array in-place with O(1) extra space.

Remove Duplicates from Sorted Array

function removeDuplicates(nums: number[]): number {
  if (nums.length === 0) return 0;
  let writePos = 1;

  for (let i = 1; i < nums.length; i++) {
    if (nums[i] !== nums[i - 1]) {
      nums[writePos] = nums[i];
      writePos++;
    }
  }

  return writePos;
}

Dutch National Flag (3-way partition)

Partition an array into three sections. Classic: sort colors (0s, 1s, 2s).

function sortColors(nums: number[]): void {
  let low = 0;
  let mid = 0;
  let high = nums.length - 1;

  while (mid <= high) {
    if (nums[mid] === 0) {
      [nums[low], nums[mid]] = [nums[mid], nums[low]];
      low++;
      mid++;
    } else if (nums[mid] === 1) {
      mid++;
    } else {
      [nums[mid], nums[high]] = [nums[high], nums[mid]];
      high--;
      // don't increment mid -- need to check swapped element
    }
  }
}

String Patterns

Valid Anagram

function isAnagram(s: string, t: string): boolean {
  if (s.length !== t.length) return false;

  const count = new Map<string, number>();
  for (const c of s) {
    count.set(c, (count.get(c) ?? 0) + 1);
  }
  for (const c of t) {
    const val = (count.get(c) ?? 0) - 1;
    if (val < 0) return false;
    count.set(c, val);
  }

  return true;
}

Reverse Words in a String

function reverseWords(s: string): string {
  return s.trim().split(/\s+/).reverse().join(' ');
}

Longest Common Prefix

function longestCommonPrefix(strs: string[]): string {
  if (strs.length === 0) return '';
  let prefix = strs[0];

  for (let i = 1; i < strs.length; i++) {
    while (!strs[i].startsWith(prefix)) {
      prefix = prefix.slice(0, -1);
      if (prefix === '') return '';
    }
  }

  return prefix;
}

Matrix as 2D Array

Spiral Order Traversal

function spiralOrder(matrix: number[][]): number[] {
  const result: number[] = [];
  let top = 0, bottom = matrix.length - 1;
  let left = 0, right = matrix[0].length - 1;

  while (top <= bottom && left <= right) {
    for (let j = left; j <= right; j++) result.push(matrix[top][j]);
    top++;
    for (let i = top; i <= bottom; i++) result.push(matrix[i][right]);
    right--;
    if (top <= bottom) {
      for (let j = right; j >= left; j--) result.push(matrix[bottom][j]);
      bottom--;
    }
    if (left <= right) {
      for (let i = bottom; i >= top; i--) result.push(matrix[i][left]);
      left++;
    }
  }

  return result;
}

Key Takeaways

  1. Prefix sum turns range-sum queries from O(n) to O(1). Combine with hash map for subarray-sum problems.
  2. Kadane's algorithm is the template for maximum subarray. Extend or restart at each position.
  3. In-place manipulation usually means a write pointer that trails behind a read pointer.
  4. String immutability means building strings in a loop is O(n^2). Always collect into an array and join.
  5. Two pointers on sorted arrays is covered in its own article -- it deserves the space.

When an interview problem gives you an array or string, ask yourself: "Is this a prefix sum problem? A two-pointer problem? A hash map counting problem? A sliding window problem?" That question alone eliminates most of the solution space.

On this page