Facebook Pixel

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] if index_j is a valid index at minute time_j
  • -1 if index_j is out of bounds at minute time_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 with n - t elements remaining, starting from index t of the original array
  • When t > n: The array is in the restoration phase with t - n elements restored, which are the first t - n elements of the original array
  • When t = n: The array is empty
  • When t = 0 or t = 2n: The array is in its original state
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 position t 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 index i + t in the original array
  • So if i < n - t, the answer is nums[i + t]

During restoration phase (t > n):

  • The array has t - n elements (we've added back t - n elements)
  • These are the first t - n elements of the original array in their original positions
  • So if i < t - n, the answer is nums[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:

  1. Initialize the answer array: Create an array ans of length m (number of queries) and initialize all values to -1. This handles the default case where an index is out of bounds.

  2. Process each query: For each query (t, i) in the queries array:

    a. Normalize the time: Calculate t %= 2 * n to map any time to its position within a single cycle of 2n 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), then ans[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), then ans[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.

  3. 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 Evaluator

Example 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More