2113. Elements in Array After Removing and Replacing Elements 🔒
Problem Description
You have a 0-indexed integer array nums
that undergoes a cyclic transformation process starting at minute 0:
Phase 1 - Removal Phase: Every minute, the leftmost element is removed from nums
until the array becomes empty. This takes n
minutes (where n
is the length of the original array).
Phase 2 - Restoration Phase: Every minute, one element is appended back to the end of nums
in the same order they were removed, until the original array is restored. This also takes n
minutes.
This entire cycle of 2n
minutes repeats indefinitely.
For example, with nums = [0,1,2]
:
- Minute 0:
[0,1,2]
(original) - Minute 1:
[1,2]
(removed 0) - Minute 2:
[2]
(removed 1) - Minute 3:
[]
(removed 2) - Minute 4:
[0]
(added back 0) - Minute 5:
[0,1]
(added back 1) - Minute 6:
[0,1,2]
(added back 2, cycle complete) - Minute 7:
[1,2]
(cycle repeats...)
You're given a 2D array queries
where each queries[j] = [time_j, index_j]
asks: "What is the value at position index_j
in the array at minute time_j
?"
For each query, return:
- The value
nums[index_j]
ifindex_j
is a valid index at minutetime_j
-1
ifindex_j
is out of bounds at minutetime_j
The solution leverages the cyclic nature of the transformation. Since the pattern repeats every 2n
minutes, we can use modulo arithmetic (t % 2n
) to determine the state of the array at any given time:
- When
t < n
: The array is in the removal phase withn - t
elements remaining, starting from indext
of the original array - When
t > n
: The array is in the restoration phase witht - n
elements restored, which are the firstt - n
elements of the original array - When
t = n
: The array is empty - When
t = 0
ort = 2n
: The array is in its original state
Intuition
The key insight is recognizing that the array transformation follows a predictable, cyclic pattern that repeats every 2n
minutes. Instead of simulating the entire process minute by minute, we can directly calculate what the array looks like at any given time.
Let's think about what happens during one complete cycle:
- For the first
n
minutes (removal phase), we're removing elements from the left, which is equivalent to the array "sliding left" - the element at position 0 in the current array was originally at positiont
in the original array - For the next
n
minutes (restoration phase), we're building the array back up from empty, adding elements in their original order
Since the pattern repeats every 2n
minutes, we can use modulo arithmetic to map any time t
to its equivalent position within a single cycle: t % (2n)
.
Now, for any query at time t
asking for index i
:
During removal phase (t < n
):
- The array has
n - t
elements remaining - These elements are the last
n - t
elements of the original array - The element at index
i
in the current array corresponds to indexi + t
in the original array - So if
i < n - t
, the answer isnums[i + t]
During restoration phase (t > n
):
- The array has
t - n
elements (we've added backt - n
elements) - These are the first
t - n
elements of the original array in their original positions - So if
i < t - n
, the answer isnums[i]
At the boundary (t = n
):
- The array is empty, so any query returns
-1
This mathematical relationship allows us to answer each query in O(1) time without actually simulating the transformation process.
Solution Approach
The implementation follows a direct calculation approach based on the cyclic pattern we identified:
-
Initialize the answer array: Create an array
ans
of lengthm
(number of queries) and initialize all values to-1
. This handles the default case where an index is out of bounds. -
Process each query: For each query
(t, i)
in thequeries
array:a. Normalize the time: Calculate
t %= 2 * n
to map any time to its position within a single cycle of2n
minutes.b. Check removal phase: If
t < n
, the array is in the removal phase:- The array currently has
n - t
elements - These elements start from index
t
of the original array - If
i < n - t
(index is valid), thenans[j] = nums[i + t]
- The mapping
i + t
gives us the corresponding index in the original array
c. Check restoration phase: If
t > n
, the array is in the restoration phase:- The array currently has
t - n
elements - These are the first
t - n
elements of the original array - If
i < t - n
(index is valid), thenans[j] = nums[i]
- No offset is needed since elements are in their original positions
d. Handle empty array: When
t = n
, the array is empty, so we keep the default value of-1
. - The array currently has
-
Return the result: After processing all queries, return the
ans
array.
The algorithm runs in O(m) time where m is the number of queries, with O(1) time per query. The space complexity is O(m) for storing the answer array.
The elegance of this solution lies in avoiding simulation - we directly compute the state of the array at any given time using mathematical relationships, making it highly efficient even for large time values.
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 the solution with nums = [3, 5, 7]
and queries = [[2, 0], [4, 1], [5, 2]]
.
First, we identify that n = 3
, so one complete cycle takes 2n = 6
minutes.
Initial Setup:
- Create answer array:
ans = [-1, -1, -1]
Query 1: [time=2, index=0]
- Normalize time:
t = 2 % 6 = 2
- Since
t = 2 < n = 3
, we're in the removal phase - Array has
n - t = 3 - 2 = 1
element remaining - Check if
index = 0 < 1
: Yes, it's valid - The element at index 0 in current array maps to index
0 + 2 = 2
in original array ans[0] = nums[2] = 7
- Current array state:
[7]
(elements at indices 0 and 1 were removed)
Query 2: [time=4, index=1]
- Normalize time:
t = 4 % 6 = 4
- Since
t = 4 > n = 3
, we're in the restoration phase - Array has
t - n = 4 - 3 = 1
element restored - Check if
index = 1 < 1
: No, index 1 is out of bounds ans[1]
remains-1
- Current array state:
[3]
(only first element has been restored)
Query 3: [time=5, index=2]
- Normalize time:
t = 5 % 6 = 5
- Since
t = 5 > n = 3
, we're in the restoration phase - Array has
t - n = 5 - 3 = 2
elements restored - Check if
index = 2 < 2
: No, index 2 is out of bounds (we only have indices 0 and 1) ans[2]
remains-1
- Current array state:
[3, 5]
(first two elements have been restored)
Final Result: ans = [7, -1, -1]
This example demonstrates how we directly calculate the answer for each query without simulating the entire transformation process, using the mathematical relationships between the current array state and the original array.
Solution Implementation
1class Solution:
2 def elementInNums(self, nums: List[int], queries: List[List[int]]) -> List[int]:
3 """
4 Find elements at specific indices after time-based transformations.
5
6 The array undergoes a cyclic pattern every 2n time units:
7 - First half (0 to n-1): Remove elements from front one by one
8 - Second half (n to 2n-1): Add elements back from front one by one
9
10 Args:
11 nums: Original array of integers
12 queries: List of [time, index] pairs to query
13
14 Returns:
15 List of elements at queried positions, -1 if index out of bounds
16 """
17 array_length = len(nums)
18 num_queries = len(queries)
19
20 # Initialize result array with -1 (default for out of bounds)
21 result = [-1] * num_queries
22
23 # Process each query
24 for query_idx, (time, target_index) in enumerate(queries):
25 # Normalize time to within one cycle (0 to 2n-1)
26 time_in_cycle = time % (2 * array_length)
27
28 # First half of cycle: array shrinking from the front
29 if time_in_cycle < array_length:
30 # Check if target index is within current array bounds
31 # Array has (n - time_in_cycle) elements starting from index time_in_cycle
32 if target_index < array_length - time_in_cycle:
33 # Map target index to original array position
34 result[query_idx] = nums[target_index + time_in_cycle]
35
36 # Second half of cycle: array growing from the front
37 elif time_in_cycle > array_length:
38 # Check if target index is within current array bounds
39 # Array has (time_in_cycle - n) elements starting from index 0
40 if target_index < time_in_cycle - array_length:
41 # Direct mapping since array starts from beginning
42 result[query_idx] = nums[target_index]
43
44 # When time_in_cycle == array_length, array is empty, result stays -1
45
46 return result
47
1class Solution {
2 public int[] elementInNums(int[] nums, int[][] queries) {
3 // Get the length of the original array and number of queries
4 int arrayLength = nums.length;
5 int queryCount = queries.length;
6
7 // Initialize result array to store answers for each query
8 int[] result = new int[queryCount];
9
10 // Process each query
11 for (int queryIndex = 0; queryIndex < queryCount; queryIndex++) {
12 // Default value is -1 (element doesn't exist)
13 result[queryIndex] = -1;
14
15 // Extract time and index from current query
16 int time = queries[queryIndex][0];
17 int requestedIndex = queries[queryIndex][1];
18
19 // Normalize time to be within one complete cycle (0 to 2n-1)
20 time = time % (2 * arrayLength);
21
22 // First half of cycle: array shrinks from left (elements removed from front)
23 // At time t, first t elements are removed, so array starts from index t
24 if (time < arrayLength && requestedIndex < arrayLength - time) {
25 result[queryIndex] = nums[requestedIndex + time];
26 }
27 // Second half of cycle: array grows from left (elements added back to front)
28 // At time t, (t - n) elements have been added back from the beginning
29 else if (time > arrayLength && requestedIndex < time - arrayLength) {
30 result[queryIndex] = nums[requestedIndex];
31 }
32 // When time == arrayLength, array is empty, so result remains -1
33 }
34
35 return result;
36 }
37}
38
1class Solution {
2public:
3 vector<int> elementInNums(vector<int>& nums, vector<vector<int>>& queries) {
4 // Get the size of the array and number of queries
5 int arraySize = nums.size();
6 int queryCount = queries.size();
7
8 // Initialize result vector with -1 (default for out-of-bounds)
9 vector<int> result(queryCount, -1);
10
11 // Process each query
12 for (int queryIndex = 0; queryIndex < queryCount; ++queryIndex) {
13 // Extract time and index from current query
14 int time = queries[queryIndex][0];
15 int index = queries[queryIndex][1];
16
17 // Normalize time to be within one complete cycle (2 * arraySize)
18 // The array goes through a complete cycle of shrinking and growing
19 time %= (arraySize * 2);
20
21 // First half of cycle: array is shrinking from the left
22 // Elements from position 0 to time-1 are removed
23 if (time < arraySize && index < arraySize - time) {
24 // Valid index exists in the shrinking array
25 // Original index shifts by time positions
26 result[queryIndex] = nums[index + time];
27 }
28 // Second half of cycle: array is growing from the left
29 // Array has (time - arraySize) elements added back
30 else if (time > arraySize && index < time - arraySize) {
31 // Valid index exists in the growing array
32 // Elements are added from the beginning
33 result[queryIndex] = nums[index];
34 }
35 }
36
37 return result;
38 }
39};
40
1/**
2 * Finds elements in a rotating array at specific time points
3 * @param nums - The original array of numbers
4 * @param queries - Array of [time, index] pairs to query
5 * @returns Array of elements at the queried positions, -1 if position is invalid
6 */
7function elementInNums(nums: number[], queries: number[][]): number[] {
8 // Get the length of the input array
9 const arrayLength: number = nums.length;
10
11 // Get the number of queries to process
12 const queryCount: number = queries.length;
13
14 // Initialize result array with -1 for all positions
15 const result: number[] = Array(queryCount).fill(-1);
16
17 // Process each query
18 for (let queryIndex = 0; queryIndex < queryCount; ++queryIndex) {
19 // Destructure time and position from current query
20 let [time, position] = queries[queryIndex];
21
22 // Normalize time to be within one complete cycle (2 * arrayLength)
23 // First half: elements are removed from front
24 // Second half: elements are added to back
25 time %= (2 * arrayLength);
26
27 // First phase: removing elements from the front
28 // Check if position is valid after removing 'time' elements from front
29 if (time < arrayLength && position < arrayLength - time) {
30 // Element at position 'position' after removing 'time' elements
31 result[queryIndex] = nums[position + time];
32 }
33 // Second phase: adding elements back to the array
34 // Check if position is valid when array has 'time - arrayLength' elements
35 else if (time >= arrayLength && position < time - arrayLength) {
36 // Element at position 'position' in the rebuilding array
37 result[queryIndex] = nums[position];
38 }
39 }
40
41 return result;
42}
43
Time and Space Complexity
The time complexity is O(m)
, where m
is the length of the queries
array. The algorithm iterates through each query exactly once, and for each query, it performs constant-time operations: computing t % (2 * n)
, comparing values, and potentially accessing an array element. Since the loop runs m
times and each iteration takes O(1)
time, the overall time complexity is O(m)
.
The space complexity is O(1)
when excluding the space consumed by the answer array ans
. The algorithm only uses a fixed amount of extra space for variables n
, m
, j
, t
, and i
, regardless of the input size. If we include the answer array, the space complexity would be O(m)
since we create an array of size m
to store the results.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle the Empty Array State
A critical pitfall is not properly handling when time_in_cycle == array_length
. At this exact moment, the array is completely empty, and any index query should return -1
.
Incorrect Implementation:
if time_in_cycle <= array_length: # Wrong! Includes the empty state if target_index < array_length - time_in_cycle: result[query_idx] = nums[target_index + time_in_cycle]
Why it fails: When time_in_cycle == array_length
, the expression array_length - time_in_cycle
equals 0, but trying to access nums[target_index + array_length]
will cause an index out of bounds error.
Correct Implementation:
if time_in_cycle < array_length: # Strict inequality excludes empty state if target_index < array_length - time_in_cycle: result[query_idx] = nums[target_index + time_in_cycle]
2. Incorrect Time Normalization for Negative or Zero-Length Arrays
Another pitfall is not considering edge cases in time normalization or array length.
Problematic Code:
time_in_cycle = time % (2 * array_length) # Fails when array_length is 0
Solution: Add a guard clause for empty input arrays:
if array_length == 0: return [-1] * num_queries time_in_cycle = time % (2 * array_length) if array_length > 0 else 0
3. Off-by-One Errors in Index Mapping
Mixing up the index mapping during the removal phase is common. The key insight is that when t
elements have been removed, the current array starts from index t
of the original array.
Wrong Mapping:
# Incorrect: forgetting to add the offset if time_in_cycle < array_length: if target_index < array_length - time_in_cycle: result[query_idx] = nums[target_index] # Missing offset!
Correct Mapping:
if time_in_cycle < array_length: if target_index < array_length - time_in_cycle: result[query_idx] = nums[target_index + time_in_cycle] # Correct offset
4. Misunderstanding the Restoration Phase Bounds
During restoration, the array has time_in_cycle - array_length
elements, not time_in_cycle
elements.
Incorrect Bound Check:
elif time_in_cycle > array_length: if target_index < time_in_cycle: # Wrong bound! result[query_idx] = nums[target_index]
Correct Bound Check:
elif time_in_cycle > array_length: if target_index < time_in_cycle - array_length: # Correct bound result[query_idx] = nums[target_index]
These pitfalls highlight the importance of carefully tracking array bounds and state transitions throughout the cyclic transformation process.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!