Facebook Pixel

2869. Minimum Operations to Collect Elements

EasyBit ManipulationArrayHash Table
Leetcode Link

Problem Description

You have an array nums containing positive integers and a positive integer k.

You can perform operations where each operation removes the last element from the array and adds it to your collection.

Your goal is to collect all integers from 1 to k (inclusive). You need to find the minimum number of operations required to achieve this.

For example, if k = 3, you need to collect the integers 1, 2, and 3 from the array by removing elements from the end.

The solution works by traversing the array from right to left (since we can only remove from the end). It keeps track of which numbers from 1 to k have been collected using a boolean array is_added.

For each element encountered:

  • If the element is greater than k, it's ignored since we don't need it
  • If the element is between 1 and k and hasn't been collected yet, it's marked as collected
  • The process continues until all numbers from 1 to k have been collected

The function returns the total number of operations needed, which equals the number of elements that had to be removed from the end of the array to collect all required numbers.

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

Intuition

Since we can only remove elements from the end of the array, we must work backwards. This is the key insight - we're forced to take elements in a specific order (from right to left).

Think of it like peeling layers from an onion - we can only access the outermost layer first. To collect all numbers from 1 to k, we need to keep removing elements from the end until we've seen all required numbers at least once.

The greedy approach works here because:

  1. We have no choice in the order of removal (must be from the end)
  2. Once we've collected a number, we don't need to collect it again
  3. We want to stop as soon as we have all numbers from 1 to k

By traversing from right to left, we're simulating the removal process without actually modifying the array. We track which numbers we've "collected" using a boolean array. Elements greater than k are irrelevant since we only need numbers from 1 to k. Duplicate occurrences of already-collected numbers are also skipped.

The minimum operations equals the position from the end where we complete our collection. If we collect all k numbers after examining m elements from the right, then we needed exactly m removal operations.

This reverse traversal with tracking gives us an O(n) time solution, where we process each element at most once and stop as soon as our collection is complete.

Solution Approach

The solution uses a reverse traversal approach with a boolean tracking array to efficiently find the minimum operations needed.

Implementation Steps:

  1. Initialize tracking structures:

    • Create a boolean array is_added of size k to track which numbers from 1 to k have been collected
    • Initialize a counter count to track how many unique numbers we've collected
    • Store the array length n for iteration
  2. Traverse the array in reverse order:

    for i in range(n - 1, -1, -1):

    This simulates removing elements from the end without modifying the array.

  3. Process each element:

    • Skip if the element is greater than k (we don't need it):
      if nums[i] > k:
          continue
    • Skip if we've already collected this number:
      if is_added[nums[i] - 1]:
          continue
    • Note: We use nums[i] - 1 as the index since arrays are 0-indexed but we need numbers 1 to k
  4. Mark collection and check completion:

    • Mark the number as collected: is_added[nums[i] - 1] = True
    • Increment the count of collected numbers: count += 1
    • Check if we've collected all k numbers:
      if count == k:
          return n - i
    • The return value n - i represents the number of elements we had to examine (and thus remove) from the end

Time Complexity: O(n) where n is the length of the array, as we traverse the array once in the worst case.

Space Complexity: O(k) for the boolean tracking array.

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 an example with nums = [3, 1, 5, 4, 2] and k = 3.

We need to collect integers 1, 2, and 3 from the array by removing elements from the end.

Initial Setup:

  • n = 5 (array length)
  • is_added = [False, False, False] (tracking array for numbers 1, 2, 3)
  • count = 0 (numbers collected so far)

Step 1: i = 4 (element = 2)

  • Element 2 is ≤ k (3), so we process it
  • is_added[1] is False (haven't collected 2 yet)
  • Mark as collected: is_added = [False, True, False]
  • Increment count: count = 1
  • Haven't collected all k numbers yet, continue

Step 2: i = 3 (element = 4)

  • Element 4 is > k (3), so we skip it
  • Continue to next element

Step 3: i = 2 (element = 5)

  • Element 5 is > k (3), so we skip it
  • Continue to next element

Step 4: i = 1 (element = 1)

  • Element 1 is ≤ k (3), so we process it
  • is_added[0] is False (haven't collected 1 yet)
  • Mark as collected: is_added = [True, True, False]
  • Increment count: count = 2
  • Haven't collected all k numbers yet, continue

Step 5: i = 0 (element = 3)

  • Element 3 is ≤ k (3), so we process it
  • is_added[2] is False (haven't collected 3 yet)
  • Mark as collected: is_added = [True, True, True]
  • Increment count: count = 3
  • We've collected all k numbers!
  • Return n - i = 5 - 0 = 5

Result: We need 5 operations (removing all elements from the array) to collect integers 1, 2, and 3.

The elements would be removed in this order: 2, 4, 5, 1, 3, collecting the required numbers {2}, {2}, {2}, {1, 2}, {1, 2, 3}.

Solution Implementation

1class Solution:
2    def minOperations(self, nums: List[int], k: int) -> int:
3        # Track which numbers from 1 to k have been seen
4        seen_numbers = [False] * k
5      
6        # Count of unique numbers from 1 to k found so far
7        unique_count = 0
8      
9        # Get the length of the input array
10        array_length = len(nums)
11      
12        # Traverse the array from right to left
13        for index in range(array_length - 1, -1, -1):
14            current_num = nums[index]
15          
16            # Skip if number is out of range [1, k] or already seen
17            if current_num > k or current_num < 1 or seen_numbers[current_num - 1]:
18                continue
19          
20            # Mark this number as seen (using 0-based indexing)
21            seen_numbers[current_num - 1] = True
22            unique_count += 1
23          
24            # If we've found all k numbers, return the count of elements removed
25            if unique_count == k:
26                return array_length - index - 1
27      
28        # Return the total length if not all k numbers were found
29        return array_length
30
1class Solution {
2    public int minOperations(List<Integer> nums, int k) {
3        // Track which numbers from 1 to k have been found
4        boolean[] isFound = new boolean[k];
5      
6        // Get the size of the input list
7        int n = nums.size();
8      
9        // Counter for unique numbers from 1 to k that have been found
10        int uniqueCount = 0;
11      
12        // Traverse the list from right to left (backwards)
13        for (int i = n - 1; ; i--) {
14            int currentNum = nums.get(i);
15          
16            // Skip if current number is out of range [1, k] or already found
17            if (currentNum > k || currentNum < 1 || isFound[currentNum - 1]) {
18                continue;
19            }
20          
21            // Mark the current number as found (using 0-based indexing)
22            isFound[currentNum - 1] = true;
23            uniqueCount++;
24          
25            // If all k unique numbers have been found
26            if (uniqueCount == k) {
27                // Return the number of elements removed from the end
28                return n - i;
29            }
30        }
31    }
32}
33
1class Solution {
2public:
3    int minOperations(vector<int>& nums, int k) {
4        int n = nums.size();
5      
6        // Track which numbers from 1 to k have been encountered
7        vector<bool> isAdded(n);
8      
9        // Counter for unique numbers from 1 to k found so far
10        int count = 0;
11      
12        // Traverse the array from right to left
13        for (int i = n - 1;; --i) {
14            // Skip if current number is greater than k or already encountered
15            if (nums[i] > k || isAdded[nums[i] - 1]) {
16                continue;
17            }
18          
19            // Mark this number as encountered (using 0-indexed array)
20            isAdded[nums[i] - 1] = true;
21          
22            // Increment count and check if all k numbers are found
23            if (++count == k) {
24                // Return the number of elements from current position to end
25                return n - i;
26            }
27        }
28    }
29};
30
1/**
2 * Finds the minimum number of operations to collect all numbers from 1 to k
3 * by removing elements from the end of the array
4 * @param nums - The input array of numbers
5 * @param k - The target number (collect all integers from 1 to k)
6 * @returns The minimum number of elements to remove from the end
7 */
8function minOperations(nums: number[], k: number): number {
9    const arrayLength: number = nums.length;
10  
11    // Track which numbers from 1 to k have been collected
12    const isCollected: boolean[] = Array(k).fill(false);
13  
14    // Counter for unique numbers collected in range [1, k]
15    let collectedCount: number = 0;
16  
17    // Iterate from the end of array backwards
18    for (let currentIndex: number = arrayLength - 1; ; currentIndex--) {
19        // Skip if current number is out of range or already collected
20        if (nums[currentIndex] > k || isCollected[nums[currentIndex] - 1]) {
21            continue;
22        }
23      
24        // Mark current number as collected (using 0-based indexing)
25        isCollected[nums[currentIndex] - 1] = true;
26        collectedCount++;
27      
28        // Check if all k numbers have been collected
29        if (collectedCount === k) {
30            // Return the number of elements removed from the end
31            return arrayLength - currentIndex;
32        }
33    }
34}
35

Time and Space Complexity

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

The algorithm iterates through the array once from right to left (from index n-1 to 0). In each iteration, it performs constant-time operations:

  • Checking if nums[i] > k takes O(1)
  • Checking and updating is_added[nums[i] - 1] takes O(1)
  • Incrementing count and comparing with k takes O(1)

Since we traverse the array at most once and perform O(1) operations per element, the overall time complexity is O(n).

Space Complexity: O(k)

The algorithm uses a boolean array is_added of size k to track which numbers from 1 to k have been encountered. Additional variables (count, n, i) use constant space O(1). Therefore, the total space complexity is O(k).

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

Common Pitfalls

1. Off-by-One Error in Return Value

The most common mistake is incorrectly calculating the number of operations when returning the result. When we find all k numbers at index i, the operations needed should be n - i, not n - i - 1.

Incorrect:

if unique_count == k:
    return array_length - index - 1  # Wrong!

Correct:

if unique_count == k:
    return array_length - index  # Correct

Why: When we're at index i, we've already "removed" elements from index n-1 down to index i. The count of removed elements is (n-1) - i + 1 = n - i.

2. Not Handling Numbers Outside Range [1, k]

Forgetting to check if the current number is within the valid range can cause index out of bounds errors or incorrect logic.

Incorrect:

# Missing boundary check
if not seen_numbers[current_num - 1]:  # Can crash if current_num > k or current_num <= 0
    seen_numbers[current_num - 1] = True

Correct:

# Check boundaries first
if current_num > k or current_num < 1:
    continue
if not seen_numbers[current_num - 1]:
    seen_numbers[current_num - 1] = True

3. Using Wrong Array Indexing

Since we need numbers from 1 to k but arrays are 0-indexed, forgetting to adjust the index is a common error.

Incorrect:

seen_numbers[current_num] = True  # Wrong! Will miss number 1 and potentially go out of bounds

Correct:

seen_numbers[current_num - 1] = True  # Maps number 1 to index 0, 2 to index 1, etc.

4. Not Handling Duplicate Numbers

Failing to skip already collected numbers will lead to incorrect counting and premature termination.

Incorrect:

for index in range(array_length - 1, -1, -1):
    if 1 <= nums[index] <= k:
        unique_count += 1  # Wrong! Counts duplicates
        if unique_count == k:
            return array_length - index

Correct:

for index in range(array_length - 1, -1, -1):
    if 1 <= nums[index] <= k and not seen_numbers[nums[index] - 1]:
        seen_numbers[nums[index] - 1] = True
        unique_count += 1  # Only increment for new unique numbers
        if unique_count == k:
            return array_length - index

5. Edge Case: Not All k Numbers Present

If the array doesn't contain all numbers from 1 to k, the function should return the total array length (all elements need to be removed).

Solution: Always have a fallback return statement after the loop:

# After the loop completes without finding all k numbers
return array_length
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:


Recommended Readings

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

Load More