2869. Minimum Operations to Collect Elements
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
andk
and hasn't been collected yet, it's marked as collected - The process continues until all numbers from
1
tok
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.
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:
- We have no choice in the order of removal (must be from the end)
- Once we've collected a number, we don't need to collect it again
- We want to stop as soon as we have all numbers from
1
tok
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:
-
Initialize tracking structures:
- Create a boolean array
is_added
of sizek
to track which numbers from1
tok
have been collected - Initialize a counter
count
to track how many unique numbers we've collected - Store the array length
n
for iteration
- Create a boolean array
-
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.
-
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 numbers1
tok
- Skip if the element is greater than
-
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
- Mark the number as collected:
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 EvaluatorExample 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
takesO(1)
- Checking and updating
is_added[nums[i] - 1]
takesO(1)
- Incrementing
count
and comparing withk
takesO(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
In a binary min heap, the minimum element can be found in:
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!