Reading

In-Place Array Patterns

Use read/write boundaries and safe traversal direction to transform arrays without losing unread values.

70 min 4 objectives
Learning objectives
  • Maintain a read-write pointer invariant
  • Explain why some merges must proceed backward
  • Rotate an array with three reversals
  • Test boundary cases that expose pointer errors

In place means memory is part of the problem

An in-place problem asks you to reuse the input storage rather than build another full-sized array. The hardest part is usually not saving memory; it is ensuring that a write never destroys a value you still need to read.

Read/write compaction

Move Zeroes (LeetCode #283) asks you to move zeroes to the end while preserving the relative order of nonzero values, using the same array. Repeated splice is both risky and O(n²): each removal shifts later elements, and forward iteration can skip the value that slides into the removed slot.

Instead, let read inspect every value and let write mark the next finished position:

Code
function moveZeroes(nums: number[]): void {
  let write = 0

  for (let read = 0; read < nums.length; read += 1) {
    if (nums[read] !== 0) nums[write++] = nums[read]
  }
  while (write < nums.length) nums[write++] = 0
}

The invariant is:

Before each read, positions [0, write) contain all nonzero values seen so far, in their original order.

Remove Duplicates from Sorted Array (LeetCode #26) uses the same skeleton. Because equal values are adjacent, keep a value only when it differs from the last value written:

Code
function removeDuplicates(nums: number[]): number {
  if (nums.length === 0) return 0
  let write = 1
  for (let read = 1; read < nums.length; read += 1) {
    if (nums[read] !== nums[write - 1]) nums[write++] = nums[read]
  }
  return write
}

Only the first write positions are promised as output. Values after that prefix are irrelevant leftovers, not a bug.

When forward writing destroys data

Merge Sorted Array (LeetCode #88) gives nums1 with m sorted values followed by n reserved slots, plus nums2 with n sorted values. You must merge into nums1. Writing the smallest value from the front can overwrite a real nums1 value that has not been merged. The safe empty space is at the back, so write the largest value first:

Code
function merge(nums1: number[], m: number, nums2: number[], n: number): void {
  let first = m - 1
  let second = n - 1
  let write = m + n - 1

  while (second >= 0) {
    if (first >= 0 && nums1[first] > nums2[second]) {
      nums1[write--] = nums1[first--]
    } else {
      nums1[write--] = nums2[second--]
    }
  }
}

The loop condition is second >= 0, not “while either array remains.” If nums2 is finished, nothing needs copying. The first >= 0 guard handles the opposite case, where nums1 runs out and all remaining values must come from nums2.

Rotation by three reversals

Rotate Array (LeetCode #189) asks you to rotate right by k positions in place. Normalize k first because it may exceed the array length: k %= nums.length.

For [1,2,3,4,5,6,7] and k = 3:

Code
reverse everything     [7,6,5,4,3,2,1]
reverse first 3        [5,6,7,4,3,2,1]
reverse the remainder  [5,6,7,1,2,3,4]
Code
function reverseRange(nums: number[], left: number, right: number): void {
  while (left < right) {
    ;[nums[left], nums[right]] = [nums[right], nums[left]]
    left += 1
    right -= 1
  }
}

function rotate(nums: number[], k: number): void {
  if (nums.length === 0) return
  k %= nums.length
  reverseRange(nums, 0, nums.length - 1)
  reverseRange(nums, 0, k - 1)
  reverseRange(nums, k, nums.length - 1)
}

Boundary tests are part of the algorithm

Trace an empty array, one value, all removable values, no removable values, k = 0, k = length, and a merge where either input runs out first. Pointer solutions often look right on the central example and fail only when two boundaries begin or end together.

Common mistakes

  • Deleting while iterating forward and skipping shifted elements.
  • Treating the entire mutated tail as meaningful after compaction.
  • Merging from the front and overwriting unread data.
  • Forgetting k %= length or dividing by zero on an empty array.
  • Mixing inclusive and exclusive right boundaries in a reverse helper.
Checkpoint

State the safe region

For each algorithm, point to the part of the array that is already final and explain why the next write cannot destroy unread information.