2869. Minimum Operations to Collect Elements
Problem Description
You are given an array of positive integers, named nums
, and a target number represented by the integer k
. Your goal is to collect a list of elements labeled from 1
to k
through a specific operation. The operation is defined as removing the last element from the array nums
and placing that element in your collection. You must determine the fewest number of operations needed to collect all elements from 1
to k
. If an element is not present in nums
or cannot be obtained by performing the allowed operations, it implies that it is impossible to collect all elements from 1
to k
.
For example, if your array nums
is [1,3,2,4,3]
and k
is 3
, you can perform the following operations:
- Remove
3
fromnums
and add it to the collection. Now your collection has[3]
andnums
is[1,3,2,4]
. - Remove
4
fromnums
but do not add to the collection since4
is not needed (we want elements from1
tok=3
). - Remove
2
fromnums
and it goes to the collection. Your collection is now[3,2]
andnums
is[1,3]
. - Finally, remove
3
fromnums
and since it’s already in the collection, you can ignore it. - Remove
1
fromnums
and add it to your collection.
After these 4 operations, your collection has the elements [1, 2, 3]
, and thus, the minimum number of operations is 4
. The task is to figure out this minimum number of operations for any given array nums
and integer k
.
Intuition
The intuition behind the solution is to address the problem efficiently by working backwards, starting from the end of the array - since it's the only place where we can remove elements - and moving towards the front. By doing this, we keep a check on what elements are getting added to the collection and ensure the following:
- We only care about elements that are less than or equal to
k
, because we want to collect elements1
tok
. - We avoid adding duplicates to our collection because each number from
1
tok
should only be collected once.
We use a list called is_added
that keeps track of whether an element has been added to the collection. This array is of size k
, where each index represents an element from 1
to k
, and the value at each index represents whether the corresponding element has been added to the collection.
If we encounter an element, while traversing from the end, that is less than or equal to k
and has not been added (is_added[element - 1]
is False
), we mark it as added and increase our count of unique elements. We continue this process until our count reaches k
, which means we have all the elements from 1
to k
.
The number of operations is then simply the total number of elements in nums
minus the index of the last added element because all elements from the end of the array to this index have to be removed in order to collect all elements from 1
to k
. Essentially, we're tracking how many removals we made from the end of the array to get all elements from 1
to k
.
Solution Approach
The solution involves a single pass through the given array in reverse order, beginning from the last element and moving towards the first. This strategy is chosen because elements can only be removed from the end of the array. The language of choice for the implementation is Python.
Here's a step-by-step explanation of the solution with reference to the provided code snippet:
-
An array
is_added
of sizek
is created to keep track of elements from1
tok
that have been added to our collection. Initially, all values inis_added
are set toFalse
, indicating that no elements have been collected yet. -
We define a count variable
count
, which keeps the count of unique elements that we have collected so far, starting the count from0
. -
We iterate through the array
nums
from the last element to the first, using a reverse loop. This is implemented by the loopfor i in range(n - 1, -1, -1):
, wheren
is the length of thenums
array. -
For each element
nums[i]
encountered during the traversal:-
We check if
nums[i]
is greater thank
or ifis_added[nums[i] - 1]
isTrue
(the element has already been added to the collection). If either condition is true, we continue to the next iteration without performing any operations since we either don't need the element or have already collected it. -
If
nums[i]
is needed and has not been added to the collection yet, we setis_added[nums[i] - 1]
toTrue
and increment ourcount
. -
As soon as our
count
equalsk
, we know we have collected all elements from1
up tok
. The minimum number of operations required is then the total length of the array minus the current indexi
, which gives usn - i
.
-
This approach efficiently ensures that we do not collect unnecessary elements and simultaneously avoid duplication in our collection. Once we've collected all required elements, we immediately return the result.
Using this algorithm, we leverage simple data structures such as an array (is_added
) and a counter variable to reach an optimal solution with a time complexity of O(n), where n is the number of elements in nums
.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small example to illustrate the solution approach with the array nums = [5,4,2,3,1,2]
and target k = 3
.
-
The
is_added
array will be initialized with values[False, False, False]
which signifies that none of the numbers1
,2
, or3
have been added to our collection yet. -
We iterate over
nums
starting from the last element, so our loop begins atnums[5]
which is2
.- Since
2
is less than or equal tok
and hasn't been added to the collection yet (is_added[2 - 1]
isFalse
), we add it by settingis_added[1]
toTrue
. Our collection becomes[False, True, False]
, indicating that2
has been added.
- Since
-
Moving to
nums[4]
which is1
:- Following our rules, we add
1
to the collection andis_added
becomes[True, True, False]
.
- Following our rules, we add
-
Next, we look at
nums[3]
which is3
: -
3
is less than or equal tok
and is not present in the collection (is_added[3 - 1]
isFalse
), so we add it. Ouris_added
array now becomes[True, True, True]
. At this point, we have all elements from1
tok
in our collection, and we stop the iteration.
Since we have collected all the numbers from 1
to k
after reaching the 3rd index from the right (inclusive), we have done a total of 6 (length of nums) - 3 (current index) = 3
operations to collect all necessary elements. Therefore, the fewest number of operations required in this example is 3
.
Solution Implementation
1from typing import List
2
3class Solution:
4 def min_operations(self, nums: List[int], k: int) -> int:
5 # Create a list to track if the required numbers have been added
6 is_added = [False] * k
7 count = 0 # Counter for unique numbers added
8 n = len(nums) # Calculate the length of the nums list
9
10 # Start iterating over the list from the end to the beginning
11 for i in range(n - 1, -1, -1):
12 if nums[i] > k or is_added[nums[i] - 1]:
13 # Skip if the number is greater than k or already added
14 continue
15
16 # Mark the number as added
17 is_added[nums[i] - 1] = True
18 count += 1 # Increment the counter by 1
19
20 if count == k:
21 # If we have added k unique numbers, return the number of operations
22 return n - i
23
24 # If it is not possible to perform the operation, return -1
25 return -1
26
1import java.util.List;
2
3class Solution {
4 // Method to find the minimum number of operations to add the first k positive integers into the list
5 public int minOperations(List<Integer> nums, int k) {
6 // Array to keep track of which numbers between 1 to k have been added
7 boolean[] isNumberAdded = new boolean[k];
8 // Get the size of the input list
9 int n = nums.size();
10 // Counter for unique numbers added to the list
11 int count = 0;
12
13 // Iterate in reverse through the list
14 for (int i = n - 1; i >= 0; i--) {
15 int currentValue = nums.get(i);
16 // If the current value is greater than k or already marked as added, skip it
17 if (currentValue > k || isNumberAdded[currentValue - 1]) {
18 continue;
19 }
20 // Mark this number as added
21 isNumberAdded[currentValue - 1] = true;
22 // Increment the count of unique numbers
23 count++;
24 // If we have added k unique numbers, return the number of operations
25 if (count == k) {
26 return n - i;
27 }
28 }
29 // If we exit the loop without returning, there's an error, so return -1 as it shouldn't happen
30 // Each number between 1 and k should exist in a properly-sized list
31 return -1;
32 }
33}
34
1#include <vector>
2using namespace std;
3
4class Solution {
5public:
6 // This function calculates the minimum number of operations required
7 // to reduce array 'nums' such that there are 'k' distinct integers
8 int minOperations(vector<int>& nums, int k) {
9 int n = nums.size(); // Obtain the size of nums
10 vector<bool> isAdded(n, false); // Create a boolean vector to track added numbers
11
12 int countDistinct = 0; // Variable to count distinct integers
13 // Start from the end of the vector and look for distinct integers until 'k' are found
14 for (int i = n - 1;; --i) {
15 // If current number is greater than k or already counted as distinct, skip it
16 if (nums[i] > k || isAdded[nums[i] - 1]) {
17 continue;
18 }
19
20 // Mark the number as added because it is distinct
21 isAdded[nums[i] - 1] = true;
22
23 // Increase the count of distinct numbers
24 countDistinct++;
25
26 // If we have found 'k' distinct numbers, return the number of operations,
27 // which is the difference between array length and starting index
28 if (countDistinct == k) {
29 return n - i;
30 }
31 }
32
33 // Note that the loop is missing an exit condition and might lead
34 // to an out of bounds access in the nums vector or an infinite loop
35 // if 'k' distinct numbers are not found.
36 // The problem's constraints should ensure that this situation doesn't happen.
37 }
38};
39
1function minOperations(nums: number[], k: number): number {
2 // Get the length of the input array.
3 const arrayLength: number = nums.length;
4
5 // Initialize an array to keep track of which numbers have been added to the sequence.
6 const isAdded: boolean[] = Array(k).fill(false);
7
8 // Initialize count to keep track of unique numbers encountered that are not more than k.
9 let uniqueCount: number = 0;
10
11 // Iterate backwards through the array.
12 for (let i: number = arrayLength - 1;; --i) {
13 // If the current number is greater than k or it has already been counted, skip it.
14 if (nums[i] > k || isAdded[nums[i] - 1]) {
15 continue;
16 }
17
18 // Mark the current number as added.
19 isAdded[nums[i] - 1] = true;
20
21 // Increment the count of unique numbers.
22 ++uniqueCount;
23
24 // If we have encountered k unique numbers, return the size of the sequence.
25 if (uniqueCount === k) {
26 return arrayLength - i;
27 }
28 }
29 // The loop was intentionally constructed to run indefinitely, control exits from within the loop.
30 // If the function has not returned within the loop, it's unexpected as per the problem statement,
31 // and may indicate an issue with the inputs. The following return statement is technically unreached.
32 return -1; // Return an impossible count as indication of an error.
33}
34
Time and Space Complexity
Time Complexity
The time complexity of the given code is O(n)
, where n
is the length of the input array nums
. This is because there is a single for loop that iterates backwards over the array nums
, and in each iteration, it performs a constant time check and assignment operation. Since these operations do not depend on the size of k
and there are no nested loops, the iteration will occur n
times, leading to a linear time complexity with respect to the size of the array.
Space Complexity
The space complexity of the given code is O(k)
. The is_added
list is the only additional data structure whose size scales with the input parameter k
. Since it is initialized to have k
boolean values, the amount of memory used by this list is directly proportional to the value of k
. The rest of the variables used within the function (like count
, n
, and i
) use a constant amount of space, and therefore do not contribute to the space complexity beyond a constant factor.
Learn more about how to find time and space complexity quickly using problem constraints.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
LeetCode 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 we
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
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!