Implement a Simulated Memory Allocator
Company: OpenAI
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates a candidate's understanding of dynamic memory management, low-level resource handling, and algorithmic data structures (e.g., segregated free lists, interval/balanced trees, buddy system) with attention to alignment, internal/external fragmentation, and block coalescing.
Constraints
- 1 <= alignment <= capacity <= 2^20
- capacity and alignment are powers of two
- 1 <= len(operations) <= 2 * 10^5
- Each operation is either ('alloc', size) with 0 <= size <= capacity, or ('free', ptr) with 0 <= ptr < capacity
Examples
Input: (16, 4, [('alloc', 3), ('alloc', 5), ('free', 0), ('alloc', 4), ('free', 8), ('free', 0), ('alloc', 12)])
Expected Output: [0, 8, 1, 0, 1, 1, 0]
Explanation: 3 bytes rounds to 4 and gets pointer 0. 5 bytes rounds to 8 and gets pointer 8. After freeing 0 and later freeing 8 and 0 again, all memory coalesces back into one 16-byte block, so the final 12-byte request rounds to 16 and is allocated at 0.
Input: (8, 8, [('alloc', 1), ('alloc', 1), ('free', 4), ('free', 0), ('alloc', 0), ('alloc', 8)])
Expected Output: [0, -1, 0, 1, -1, 0]
Explanation: With alignment 8, any positive allocation needs the whole 8-byte block. The second allocation fails, freeing pointer 4 is invalid, allocating size 0 is invalid, and after freeing pointer 0 the 8-byte block can be allocated again.
Input: (32, 4, [('alloc', 4), ('alloc', 4), ('alloc', 4), ('alloc', 4), ('free', 8), ('free', 0), ('alloc', 8), ('free', 4), ('free', 12), ('free', 8), ('alloc', 16)])
Expected Output: [0, 4, 8, 12, 1, 1, 16, 1, 1, 0, 0]
Explanation: Four 4-byte blocks fill the left half of memory. After selective frees, the allocator must coalesce multiple levels correctly. The attempt to free 8 a second time is a double-free and returns 0.
Input: (32, 8, [('alloc', 3), ('alloc', 9), ('free', 0), ('alloc', 8), ('free', 16), ('free', 0), ('alloc', 24)])
Expected Output: [0, 16, 1, 0, 1, 1, 0]
Explanation: Alignment 8 forces the first request to use an 8-byte block. The 9-byte request rounds to 16. After freeing and coalescing all buddies back to a 32-byte block, the 24-byte request rounds to 32 and gets pointer 0.
Solution
def solution(capacity, alignment, operations):
import heapq
def next_power_of_two(x):
if x <= 1:
return 1
return 1 << ((x - 1).bit_length())
max_order = capacity.bit_length() - 1
free_sets = [set() for _ in range(max_order + 1)]
free_heaps = [[] for _ in range(max_order + 1)]
def add_free(order, start):
free_sets[order].add(start)
heapq.heappush(free_heaps[order], start)
def pop_free(order):
heap = free_heaps[order]
blocks = free_sets[order]
while heap and heap[0] not in blocks:
heapq.heappop(heap)
if not heap:
return None
start = heapq.heappop(heap)
blocks.remove(start)
return start
add_free(max_order, 0)
allocated = {}
result = []
for op, value in operations:
if op == 'alloc':
size = value
if size <= 0:
result.append(-1)
continue
block_size = max(alignment, next_power_of_two(size))
if block_size > capacity:
result.append(-1)
continue
target_order = block_size.bit_length() - 1
order = target_order
while order <= max_order and not free_sets[order]:
order += 1
if order > max_order:
result.append(-1)
continue
start = pop_free(order)
while order > target_order:
order -= 1
half_size = 1 << order
right_start = start + half_size
add_free(order, right_start)
allocated[start] = target_order
result.append(start)
elif op == 'free':
ptr = value
if ptr not in allocated:
result.append(0)
continue
order = allocated.pop(ptr)
start = ptr
while order < max_order:
buddy = start ^ (1 << order)
if buddy in free_sets[order]:
free_sets[order].remove(buddy)
start = min(start, buddy)
order += 1
else:
break
add_free(order, start)
result.append(1)
else:
result.append(-1)
return resultTime complexity: O(log(capacity / alignment) * log F) amortized per operation, where F is the number of free blocks tracked in the heaps. Space complexity: O(A + F), where A is the number of currently allocated blocks and F is the number of tracked free blocks.
Hints
- Think in powers of two: each allocation belongs to a size class, and larger blocks can be split until the needed class is reached.
- When freeing a block of size S at address ptr, its buddy's address is ptr XOR S. If that buddy is also free, they can be merged.