Facebook Pixel

2460. Apply Operations to an Array

Problem Description

You have an array nums with n non-negative integers (0-indexed). You need to perform n - 1 operations on this array following these rules:

For the i-th operation (where i goes from 0 to n-2):

  • Look at elements nums[i] and nums[i + 1]
  • If they are equal, multiply nums[i] by 2 and set nums[i + 1] to 0
  • If they are not equal, skip this operation and move to the next

After completing all operations, move all zeros to the end of the array while maintaining the relative order of non-zero elements.

Key Points:

  • Operations are performed sequentially from left to right (not simultaneously)
  • Each operation compares the current element with the next element
  • When elements are equal, the first is doubled and the second becomes 0
  • After all operations, zeros are shifted to the end

Example: If you start with [1,0,2,0,0,1] and need to shift zeros to the end, you get [1,2,1,0,0,0].

The solution simulates this process by:

  1. First traversing the array and performing the doubling operations where adjacent elements are equal
  2. Creating a new array and placing all non-zero elements at the beginning
  3. The remaining positions are automatically filled with zeros

The code uses bit shifting (nums[i] <<= 1) as an efficient way to double a number (equivalent to multiplying by 2).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The problem asks us to do two distinct tasks in sequence: first modify the array based on adjacent equal elements, then rearrange the array to move zeros to the end.

Since the operations must be applied sequentially from left to right, we can't optimize or combine them - we must process each position in order. When we find nums[i] == nums[i+1], we immediately modify both values before moving to the next position. This ensures that each comparison uses the most up-to-date values.

The key insight is that these two tasks are independent:

  1. First pass: Perform all the doubling operations
  2. Second pass: Rearrange to move zeros to the end

For the first pass, a simple loop from index 0 to n-2 handles all comparisons. We check each adjacent pair and modify them according to the rule.

For the second pass, instead of trying to swap elements in place (which can be complex), we can build a new array. We iterate through the modified array and place all non-zero elements at the beginning of our result array. Since we initialize the result array with zeros, we don't need to explicitly add zeros at the end - they're already there.

The elegance of this approach is its simplicity: we don't need complex logic for shifting or swapping. By separating the two tasks and using a fresh array for the final arrangement, we avoid the complexity of in-place rearrangement while maintaining O(n) time complexity.

The use of nums[i] <<= 1 for doubling is a small optimization - left shifting by 1 bit is equivalent to multiplying by 2, but it's a faster operation at the hardware level.

Learn more about Two Pointers patterns.

Solution Approach

The solution follows a straightforward simulation approach with two distinct phases:

Phase 1: Apply Operations

We traverse the array from index 0 to n-2 (since we need to compare each element with its next neighbor):

for i in range(n - 1):
    if nums[i] == nums[i + 1]:
        nums[i] <<= 1      # Double the current element
        nums[i + 1] = 0    # Set the next element to 0

The bit shift operation <<= 1 efficiently doubles the value by shifting all bits one position to the left. For example, if nums[i] = 3 (binary: 11), then nums[i] <<= 1 makes it 6 (binary: 110).

Phase 2: Move Zeros to End

Instead of complex in-place swapping, we create a new result array initialized with zeros:

ans = [0] * n

Then we iterate through the modified nums array and place all non-zero elements at the beginning of ans:

i = 0
for x in nums:
    if x:              # If x is non-zero
        ans[i] = x     # Place it at the current position
        i += 1         # Move to the next position

The pointer i keeps track of where to place the next non-zero element. Since ans is pre-filled with zeros, we don't need to explicitly handle the zeros - they're already in the right positions at the end.

Time and Space Complexity:

  • Time: O(n) - We make two passes through the array
  • Space: O(n) - We create a new array of size n for the result

This approach elegantly separates concerns: first handle the doubling logic, then handle the rearrangement, making the code clean and easy to understand.

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 the array nums = [1, 5, 2, 2, 2, 0, 0].

Initial State: [1, 5, 2, 2, 2, 0, 0]

Phase 1: Apply Operations (comparing adjacent pairs)

  • Operation 0 (i=0): Compare nums[0]=1 and nums[1]=5

    • They're not equal, skip
    • Array: [1, 5, 2, 2, 2, 0, 0]
  • Operation 1 (i=1): Compare nums[1]=5 and nums[2]=2

    • They're not equal, skip
    • Array: [1, 5, 2, 2, 2, 0, 0]
  • Operation 2 (i=2): Compare nums[2]=2 and nums[3]=2

    • They're equal! Double nums[2] and set nums[3]=0
    • nums[2] = 2 << 1 = 4, nums[3] = 0
    • Array: [1, 5, 4, 0, 2, 0, 0]
  • Operation 3 (i=3): Compare nums[3]=0 and nums[4]=2

    • They're not equal, skip
    • Array: [1, 5, 4, 0, 2, 0, 0]
  • Operation 4 (i=4): Compare nums[4]=2 and nums[5]=0

    • They're not equal, skip
    • Array: [1, 5, 4, 0, 2, 0, 0]
  • Operation 5 (i=5): Compare nums[5]=0 and nums[6]=0

    • They're equal! Double nums[5] and set nums[6]=0
    • nums[5] = 0 << 1 = 0, nums[6] = 0
    • Array: [1, 5, 4, 0, 2, 0, 0]

Phase 2: Move Zeros to End

Create new array ans = [0, 0, 0, 0, 0, 0, 0] and pointer i = 0

  • Process nums[0]=1: Non-zero, place at ans[0], increment i to 1

    • ans = [1, 0, 0, 0, 0, 0, 0]
  • Process nums[1]=5: Non-zero, place at ans[1], increment i to 2

    • ans = [1, 5, 0, 0, 0, 0, 0]
  • Process nums[2]=4: Non-zero, place at ans[2], increment i to 3

    • ans = [1, 5, 4, 0, 0, 0, 0]
  • Process nums[3]=0: Zero, skip

  • Process nums[4]=2: Non-zero, place at ans[3], increment i to 4

    • ans = [1, 5, 4, 2, 0, 0, 0]
  • Process nums[5]=0: Zero, skip

  • Process nums[6]=0: Zero, skip

Final Result: [1, 5, 4, 2, 0, 0, 0]

The solution correctly doubled the adjacent equal elements (2 and 2 became 4 and 0) and moved all zeros to the end while maintaining the relative order of non-zero elements.

Solution Implementation

1class Solution:
2    def applyOperations(self, nums: List[int]) -> List[int]:
3        """
4        Apply operations to the array:
5        1. If adjacent elements are equal, double the first and set the second to 0
6        2. Move all non-zero elements to the front, maintaining their relative order
7      
8        Args:
9            nums: List of integers to process
10          
11        Returns:
12            List with operations applied and zeros moved to the end
13        """
14        n = len(nums)
15      
16        # Step 1: Apply the doubling operation for adjacent equal elements
17        for i in range(n - 1):
18            if nums[i] == nums[i + 1]:
19                # Double the current element (left shift by 1 is equivalent to multiply by 2)
20                nums[i] = nums[i] * 2  # More readable than <<= 1
21                # Set the next element to 0
22                nums[i + 1] = 0
23      
24        # Step 2: Move all non-zero elements to the front
25        # Initialize result array with zeros
26        result = [0] * n
27      
28        # Index to track position in result array
29        write_index = 0
30      
31        # Copy non-zero elements to the front of result array
32        for num in nums:
33            if num != 0:
34                result[write_index] = num
35                write_index += 1
36      
37        # All remaining positions are already 0 from initialization
38        return result
39
1class Solution {
2    /**
3     * Applies operations to the array and moves all zeros to the end.
4     * Operation: If two adjacent elements are equal, double the first element and set the second to 0.
5     * Then move all non-zero elements to the front while maintaining their relative order.
6     * 
7     * @param nums the input integer array
8     * @return the modified array with operations applied and zeros moved to the end
9     */
10    public int[] applyOperations(int[] nums) {
11        int arrayLength = nums.length;
12      
13        // Apply the operation: if adjacent elements are equal, 
14        // double the first one and set the second to zero
15        for (int i = 0; i < arrayLength - 1; i++) {
16            if (nums[i] == nums[i + 1]) {
17                // Double the current element using left shift (multiply by 2)
18                nums[i] = nums[i] << 1;
19                // Set the next element to zero
20                nums[i + 1] = 0;
21            }
22        }
23      
24        // Create a new array to store the result
25        int[] result = new int[arrayLength];
26        int currentIndex = 0;
27      
28        // Move all non-zero elements to the front of the result array
29        // This automatically leaves zeros at the end since array is initialized with zeros
30        for (int number : nums) {
31            if (number > 0) {
32                result[currentIndex] = number;
33                currentIndex++;
34            }
35        }
36      
37        return result;
38    }
39}
40
1class Solution {
2public:
3    vector<int> applyOperations(vector<int>& nums) {
4        int arraySize = nums.size();
5      
6        // First pass: Apply the operation to consecutive equal elements
7        // If nums[i] == nums[i+1], double nums[i] and set nums[i+1] to 0
8        for (int i = 0; i < arraySize - 1; ++i) {
9            if (nums[i] == nums[i + 1]) {
10                nums[i] *= 2;  // Double the current element (equivalent to left shift by 1)
11                nums[i + 1] = 0;  // Set the next element to zero
12            }
13        }
14      
15        // Second pass: Move all non-zero elements to the front
16        // Create a result array initialized with zeros
17        vector<int> result(arraySize);
18        int writeIndex = 0;
19      
20        // Copy all non-zero elements to the beginning of the result array
21        for (int& num : nums) {
22            if (num != 0) {
23                result[writeIndex] = num;
24                writeIndex++;
25            }
26        }
27        // All remaining positions are already zero (from initialization)
28      
29        return result;
30    }
31};
32
1/**
2 * Applies operations to an array where consecutive equal elements are modified,
3 * then moves all non-zero elements to the front while maintaining order
4 * @param nums - The input array of numbers to process
5 * @returns The modified array with operations applied and zeros moved to the end
6 */
7function applyOperations(nums: number[]): number[] {
8    const arrayLength: number = nums.length;
9  
10    // Phase 1: Apply operations to consecutive equal elements
11    // If two consecutive elements are equal, double the first and set the second to 0
12    for (let i = 0; i < arrayLength - 1; i++) {
13        if (nums[i] === nums[i + 1]) {
14            // Left shift by 1 is equivalent to multiplying by 2
15            nums[i] = nums[i] << 1;
16            nums[i + 1] = 0;
17        }
18    }
19  
20    // Phase 2: Move all non-zero elements to the front of the array
21    // Initialize result array with all zeros
22    const resultArray: number[] = Array(arrayLength).fill(0);
23  
24    // Track the current position for non-zero elements
25    let currentIndex: number = 0;
26  
27    // Iterate through the modified array and place non-zero elements at the front
28    for (const element of nums) {
29        if (element !== 0) {
30            resultArray[currentIndex] = element;
31            currentIndex++;
32        }
33    }
34  
35    return resultArray;
36}
37

Time and Space Complexity

Time Complexity: O(n), where n is the length of the array nums.

The algorithm consists of two sequential passes through the array:

  • First loop: Iterates through n-1 elements to check adjacent pairs and apply operations. Each operation (comparison, left shift, and assignment) takes O(1) time. Total: O(n-1) = O(n).
  • Second loop: Iterates through all n elements to rearrange non-zero values. Each iteration involves a constant-time check and potential assignment. Total: O(n).

Overall time complexity: O(n) + O(n) = O(n).

Space Complexity: O(n) for the total space used, or O(1) if we exclude the space required for the output array.

The algorithm allocates:

  • ans: A new array of size n to store the result, requiring O(n) space.
  • i: A single integer variable for indexing, requiring O(1) space.
  • The input array nums is modified in-place during the first loop.

If we follow the convention of excluding the output array from space complexity analysis (as the reference answer suggests), the auxiliary space complexity is O(1) since only a constant amount of extra space is used beyond the input and output.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Modifying Elements During Iteration Affects Subsequent Comparisons

The Problem: A common mistake is not realizing that when you modify nums[i+1] = 0 during operation i, this affects the next comparison at operation i+1. This can lead to unexpected behavior when consecutive equal elements appear.

Example: Consider the array [2, 2, 2, 1]:

  • Operation 0: nums[0] == nums[1] (2 == 2), so nums[0] = 4, nums[1] = 0
  • Operation 1: nums[1] == nums[2] (0 == 2), which is false, so no change
  • Operation 2: nums[2] == nums[3] (2 == 1), which is false, so no change
  • Result after operations: [4, 0, 2, 1]
  • After moving zeros: [4, 2, 1, 0]

If someone mistakenly thinks all three 2's should merge, they'd expect [8, 1, 0, 0], but that's incorrect because the operations are sequential, not simultaneous.

Solution: Process the array sequentially as intended. The current implementation is correct - each operation uses the current state of the array, not the original state.

Pitfall 2: Using In-Place Zero Movement Incorrectly

The Problem: Trying to move zeros to the end in-place using swap operations can be tricky and error-prone, especially when trying to maintain the relative order of non-zero elements.

Incorrect Approach:

# Wrong: This doesn't maintain relative order
left, right = 0, n - 1
while left < right:
    if nums[left] == 0:
        nums[left], nums[right] = nums[right], nums[left]
        right -= 1
    else:
        left += 1

This would incorrectly change [1, 0, 2, 0, 3] to [1, 3, 2, 0, 0] instead of [1, 2, 3, 0, 0].

Solution: Use the two-pointer technique correctly or create a new array as shown in the solution:

# Correct in-place approach (alternative to creating new array)
write_pos = 0
for i in range(n):
    if nums[i] != 0:
        nums[write_pos] = nums[i]
        if write_pos != i:
            nums[i] = 0
        write_pos += 1

Pitfall 3: Edge Case with All Zeros or Single Element

The Problem: Not handling edge cases properly:

  • Array with all zeros: [0, 0, 0]
  • Single element array: [5]
  • Array starting with zeros: [0, 0, 1, 1]

Solution: The current implementation handles these correctly:

  • For single element arrays, the loop range(n-1) simply doesn't execute when n=1
  • Arrays with zeros work because we check equality regardless of value
  • The zero-moving phase handles any configuration of zeros correctly

Pitfall 4: Integer Overflow with Large Numbers

The Problem: When doubling large numbers, there's a risk of integer overflow in languages with fixed integer sizes. While Python handles arbitrary precision integers, this could be an issue in other languages.

Example: If nums[i] = 2^31 - 1 (maximum 32-bit signed integer), doubling it would cause overflow in languages like Java or C++.

Solution:

  • In Python: No action needed due to arbitrary precision
  • In other languages: Add overflow checking or use appropriate data types (e.g., long long in C++)
# For languages with overflow concerns, add validation:
if nums[i] > MAX_INT // 2:
    # Handle overflow case
    pass
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following problems can be solved with backtracking (select multiple)


Recommended Readings

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

Load More