1860. Incremental Memory Leak
Problem Description
You have two memory sticks with memory1
and memory2
bits of available memory respectively. A faulty program is running that consumes an increasing amount of memory each second.
The program allocates memory following these rules:
- At the
i-th
second (starting from second 1), the program needs to allocatei
bits of memory - The allocation goes to the memory stick with more available memory
- If both sticks have equal available memory, allocate from the first memory stick (
memory1
) - If neither stick has at least
i
bits available, the program crashes
The process continues second by second:
- Second 1: allocate 1 bit to the stick with more memory
- Second 2: allocate 2 bits to the stick with more memory
- Second 3: allocate 3 bits to the stick with more memory
- And so on...
You need to return an array [crashTime, memory1_crash, memory2_crash]
where:
crashTime
is the second when the program crashes (when neither stick can accommodate the required memory)memory1_crash
is the remaining memory in stick 1 when the crash occursmemory2_crash
is the remaining memory in stick 2 when the crash occurs
For example, if memory1 = 2
and memory2 = 2
:
- Second 1: Allocate 1 bit to memory1 (both equal, choose first). Result: memory1 = 1, memory2 = 2
- Second 2: Allocate 2 bits to memory2 (it has more). Result: memory1 = 1, memory2 = 0
- Second 3: Need 3 bits but neither stick has enough. Program crashes.
- Return:
[3, 1, 0]
Intuition
The problem asks us to simulate a memory allocation process that continues until failure. Since we need to track exactly when the program crashes and the state of both memory sticks at that moment, a direct simulation is the most straightforward approach.
Let's think about what happens at each second:
- We need
i
bits at secondi
- We always choose the stick with more available memory (or stick 1 if tied)
- We continue until we can't allocate anymore
The key insight is that we don't need any complex optimization - we can simply simulate the process second by second. At each second i
, we:
- Check if we can allocate
i
bits (at least one stick must have≥ i
bits) - If yes, subtract
i
from the appropriate stick - If no, we've found our crash time
The simulation naturally terminates because the memory requirement grows linearly (1, 2, 3, 4, ...) while available memory only decreases. Eventually, we'll reach a point where i > max(memory1, memory2)
, meaning neither stick can accommodate the allocation.
The mathematical bound helps us understand why this simulation is efficient. If we sum up all memory consumed until second t-1
, we get 1 + 2 + 3 + ... + (t-1) = t×(t-1)/2
. This sum must be less than or equal to the total initial memory memory1 + memory2
. This means the crash time t
is bounded by approximately √(2×(memory1 + memory2))
, ensuring our simulation won't run for too long even with large memory values.
The greedy choice of always picking the stick with more memory is optimal for maximizing the program's runtime, as it helps balance the memory usage between the two sticks, preventing one from depleting too quickly.
Learn more about Math patterns.
Solution Approach
The solution implements a direct simulation of the memory allocation process. We start from second 1 and continue allocating memory until the program crashes.
Implementation Steps:
-
Initialize the counter: Start with
i = 1
to represent the current second (and the amount of memory to allocate). -
Main simulation loop: Continue while we can allocate memory
- The condition
i <= max(memory1, memory2)
checks if at least one stick has enough memory for the current allocation - If neither stick has
i
bits available, we've reached the crash point
- The condition
-
Memory allocation logic: At each second, determine which stick gets the allocation
- If
memory1 >= memory2
: Allocate to stick 1 (this handles both the "more memory" case and the tie-breaker rule) - Otherwise: Allocate to stick 2
- Subtract
i
bits from the chosen stick
- If
-
Increment and continue: Move to the next second by incrementing
i
-
Return the result: When the loop exits (program crashes), return
[i, memory1, memory2]
i
is the crash time (the second when allocation failed)memory1
andmemory2
are the remaining bits in each stick
Time Complexity Analysis:
As mentioned in the reference approach, the total memory consumed up to second t-1
is:
Sum from i=1 to t-1 of i = t × (t-1) / 2 ≤ memory1 + memory2
This means the crash time t
is bounded by O(√(memory1 + memory2))
, making the time complexity O(√n)
where n
is the total initial memory.
Space Complexity: O(1)
as we only use a constant amount of extra space for the counter variable.
The beauty of this solution lies in its simplicity - we don't need any complex data structures or algorithms, just a straightforward simulation that mirrors the problem description exactly.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example with memory1 = 8
and memory2 = 11
.
Initial state: memory1 = 8, memory2 = 11
Second 1: Need to allocate 1 bit
- Compare: memory1 (8) < memory2 (11)
- Allocate to memory2: 11 - 1 = 10
- State: memory1 = 8, memory2 = 10
Second 2: Need to allocate 2 bits
- Compare: memory1 (8) < memory2 (10)
- Allocate to memory2: 10 - 2 = 8
- State: memory1 = 8, memory2 = 8
Second 3: Need to allocate 3 bits
- Compare: memory1 (8) = memory2 (8) → tie, choose memory1
- Allocate to memory1: 8 - 3 = 5
- State: memory1 = 5, memory2 = 8
Second 4: Need to allocate 4 bits
- Compare: memory1 (5) < memory2 (8)
- Allocate to memory2: 8 - 4 = 4
- State: memory1 = 5, memory2 = 4
Second 5: Need to allocate 5 bits
- Compare: memory1 (5) > memory2 (4)
- Allocate to memory1: 5 - 5 = 0
- State: memory1 = 0, memory2 = 4
Second 6: Need to allocate 6 bits
- Check: Can we allocate? max(0, 4) = 4, but we need 6 bits
- Neither stick has enough memory → CRASH!
Result: Return [6, 0, 4]
- Crash time: 6
- memory1 remaining: 0
- memory2 remaining: 4
This example demonstrates:
- How we always choose the stick with more memory
- The tie-breaker rule (second 3)
- How the program continues until neither stick can accommodate the required allocation
- The final state captures exactly when and with what memory values the crash occurs
Solution Implementation
1class Solution:
2 def memLeak(self, memory1: int, memory2: int) -> List[int]:
3 """
4 Simulates a memory leak where increasing amounts of memory are allocated
5 from the stick with more available memory at each time step.
6
7 Args:
8 memory1: Initial memory available in first memory stick
9 memory2: Initial memory available in second memory stick
10
11 Returns:
12 List containing [crash_time, remaining_memory1, remaining_memory2]
13 """
14 # Start with time step 1 (amount of memory to allocate)
15 time_step = 1
16
17 # Continue allocating memory while we can satisfy the current allocation request
18 while time_step <= max(memory1, memory2):
19 # Allocate from the memory stick with more available memory
20 # If tied, allocate from memory1 (has priority)
21 if memory1 >= memory2:
22 memory1 -= time_step
23 else:
24 memory2 -= time_step
25
26 # Move to next time step (next allocation size)
27 time_step += 1
28
29 # Return the crash time and remaining memory in both sticks
30 return [time_step, memory1, memory2]
31
1class Solution {
2 /**
3 * Simulates memory allocation until insufficient memory is available.
4 * At each second i, allocates i units of memory from the stick with more available memory.
5 * If both have equal memory, allocates from memory1.
6 *
7 * @param memory1 Initial available memory in the first memory stick
8 * @param memory2 Initial available memory in the second memory stick
9 * @return Array containing [crashTime, remainingMemory1, remainingMemory2]
10 */
11 public int[] memLeak(int memory1, int memory2) {
12 // Start allocation from second 1
13 int currentSecond = 1;
14
15 // Continue allocating memory until neither stick can accommodate the current allocation size
16 while (currentSecond <= Math.max(memory1, memory2)) {
17 // Allocate from the memory stick with more available memory
18 // In case of a tie, allocate from memory1
19 if (memory1 >= memory2) {
20 memory1 -= currentSecond;
21 } else {
22 memory2 -= currentSecond;
23 }
24
25 // Move to the next second
26 currentSecond++;
27 }
28
29 // Return the crash time and remaining memory in both sticks
30 return new int[] {currentSecond, memory1, memory2};
31 }
32}
33
1class Solution {
2public:
3 vector<int> memLeak(int memory1, int memory2) {
4 // Start with the first allocation size (1 bit)
5 int allocationSize = 1;
6
7 // Continue allocating memory while at least one stick has enough space
8 // for the current allocation size
9 while (allocationSize <= max(memory1, memory2)) {
10 // Allocate from the stick with more available memory
11 // In case of a tie, prefer memory1 (stick 1)
12 if (memory1 >= memory2) {
13 memory1 -= allocationSize;
14 } else {
15 memory2 -= allocationSize;
16 }
17
18 // Increment allocation size for next iteration
19 allocationSize++;
20 }
21
22 // Return the crash time and remaining memory in both sticks
23 // allocationSize represents the first allocation that couldn't be satisfied
24 return {allocationSize, memory1, memory2};
25 }
26};
27
1/**
2 * Simulates memory allocation between two memory banks
3 * Allocates memory in increasing chunks starting from 1 MB
4 * Always allocates from the memory bank with more available space
5 *
6 * @param memory1 - Available memory in the first bank (in MB)
7 * @param memory2 - Available memory in the second bank (in MB)
8 * @returns Array containing [crashTime, remainingMemory1, remainingMemory2]
9 * where crashTime is the first allocation size that cannot be fulfilled
10 */
11function memLeak(memory1: number, memory2: number): number[] {
12 // Current allocation size in MB, starting from 1
13 let allocationSize: number = 1;
14
15 // Continue allocating until neither memory bank can fulfill the request
16 while (allocationSize <= Math.max(memory1, memory2)) {
17 // Allocate from the memory bank with more available space
18 if (memory1 >= memory2) {
19 // Deduct from memory1 if it has more or equal space
20 memory1 -= allocationSize;
21 } else {
22 // Deduct from memory2 if it has more space
23 memory2 -= allocationSize;
24 }
25
26 // Increment allocation size for next iteration
27 allocationSize++;
28 }
29
30 // Return the crash time and remaining memory in both banks
31 return [allocationSize, memory1, memory2];
32}
33
Time and Space Complexity
Time Complexity: O(√(memory1 + memory2))
The algorithm simulates a memory leak by repeatedly allocating memory blocks of increasing size (1, 2, 3, ..., i) from the memory stick with more available memory. The loop continues while i <= max(memory1, memory2)
.
To understand why the complexity is O(√(memory1 + memory2))
:
- The total memory allocated after
n
iterations is1 + 2 + 3 + ... + n = n(n+1)/2
- The loop stops approximately when
n(n+1)/2 ≈ memory1 + memory2
- Solving for
n
:n² ≈ 2(memory1 + memory2)
- Therefore,
n ≈ √(2(memory1 + memory2))
, which isO(√(memory1 + memory2))
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space. It modifies the input values in-place and uses only one additional variable i
to track the current iteration, regardless of the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Tie-Breaking Logic
A common mistake is misunderstanding when to allocate to memory1 vs memory2, especially in the tie-breaking scenario.
Incorrect Implementation:
# WRONG: This reverses the tie-breaking rule if memory2 > memory1: memory2 -= time_step elif memory1 > memory2: memory1 -= time_step else: # Equal case memory1 -= time_step
Why it's wrong: This code allocates to memory2 when it has strictly more memory, which seems logical but doesn't match the problem's requirement. The problem states to allocate to the stick with "more available memory" but when equal, use memory1.
Correct Implementation:
# CORRECT: memory1 gets allocation when >= memory2 if memory1 >= memory2: memory1 -= time_step else: memory2 -= time_step
2. Off-by-One Error in Loop Condition
Another pitfall is using the wrong comparison operator in the loop condition.
Incorrect Implementation:
# WRONG: Uses strict inequality
while time_step < max(memory1, memory2):
# allocation logic
time_step += 1
Why it's wrong: This would exit the loop one iteration too early. If max(memory1, memory2) = 5
and time_step = 5
, we should still try to allocate 5 bits, but this condition would exit before attempting it.
Correct Implementation:
# CORRECT: Uses <= to include the boundary case
while time_step <= max(memory1, memory2):
# allocation logic
time_step += 1
3. Starting from Wrong Time Step
Incorrect Implementation:
# WRONG: Starting from 0
time_step = 0
while time_step <= max(memory1, memory2):
time_step += 1 # Increment first
if memory1 >= memory2:
memory1 -= time_step
else:
memory2 -= time_step
Why it's wrong: The problem clearly states that at the i-th second (starting from second 1), we allocate i bits. Starting from 0 and incrementing first would allocate 1 bit at second 0, which doesn't match the problem description.
4. Checking Individual Memory Instead of Maximum
Incorrect Implementation:
# WRONG: Checking if both have enough memory while memory1 >= time_step or memory2 >= time_step: if memory1 >= memory2: memory1 -= time_step else: memory2 -= time_step time_step += 1
Why it's wrong: This is actually correct logically, but using max(memory1, memory2)
is cleaner and more efficient. However, a real mistake would be:
# WRONG: Requires both to have enough memory while memory1 >= time_step and memory2 >= time_step: # allocation logic
This would crash too early - we only need ONE stick to have enough memory to continue.
Best Practice Solution:
def memLeak(self, memory1: int, memory2: int) -> List[int]:
time_step = 1
# Continue while at least one stick can handle the allocation
while time_step <= max(memory1, memory2):
# Allocate to stick with more memory (memory1 if tied)
if memory1 >= memory2:
memory1 -= time_step
else:
memory2 -= time_step
time_step += 1
return [time_step, memory1, memory2]
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!