2260. Minimum Consecutive Cards to Pick Up
Problem Description
You have an array of integer cards where cards[i]
represents the value of the i-th card. Two cards are considered matching if they have the same value.
Your task is to find the minimum number of consecutive cards you need to pick up such that among those picked cards, there exists at least one pair of matching cards.
For example:
- If
cards = [3, 4, 2, 3, 4, 7]
, you could pick up cards from index 0 to 3 (cards[3, 4, 2, 3]
), which contains a matching pair of 3s. This requires picking up 4 consecutive cards. - Alternatively, you could pick up cards from index 1 to 4 (cards
[4, 2, 3, 4]
), which contains a matching pair of 4s. This also requires picking up 4 consecutive cards. - The minimum would be picking up cards from index 2 to 4 (cards
[2, 3, 4]
), wait that doesn't have a match. Actually from index 0 to 3 gives us 4 cards with matching 3s.
The goal is to minimize the number of consecutive cards picked while ensuring at least one matching pair exists.
If no matching pair can be found in the entire array (all cards have unique values), return -1
.
The key insight is that you want to find two cards with the same value that are as close together as possible in the array, as this minimizes the number of consecutive cards needed to include both matching cards.
Intuition
To find the minimum number of consecutive cards containing a matching pair, we need to think about what we're really looking for: two cards with the same value that are as close together as possible in the array.
Consider this: if we have two matching cards at positions i
and j
(where j > i
), the minimum number of consecutive cards we need to pick up to include both is j - i + 1
. This is because we must pick all cards from position i
to position j
inclusive.
The key realization is that we don't need to check every possible subarray. Instead, for each card value, we only care about the closest pair of that value. Why? Because if we have three cards with value x
at positions a
, b
, and c
(where a < b < c
), the smallest consecutive segment would be either from a
to b
or from b
to c
, never from a
to c
.
This leads us to a single-pass solution: as we traverse the array, we keep track of the last position where we saw each card value. When we encounter a card value we've seen before, we calculate the distance from its last occurrence. This distance represents the number of consecutive cards needed to include this matching pair.
By maintaining a hash table last
that stores the most recent index of each card value, we can efficiently:
- Check if we've seen the current card value before
- Calculate the distance if we have:
current_index - last_index + 1
- Update our minimum answer if this distance is smaller
- Update the last seen position for this card value
This way, we automatically find the closest matching pairs for each value in just one pass through the array, giving us an optimal O(n)
time complexity solution.
Learn more about Sliding Window patterns.
Solution Approach
We use a hash table approach to track the most recent position of each card value as we traverse the array.
Algorithm Steps:
-
Initialize variables:
last = {}
: A hash table to store the most recent index where each card value was seenans = inf
: Initialize the answer to infinity to track the minimum distance found
-
Single pass through the array: For each card at index
i
with valuex
:- Check if we've seen this value before: If
x in last
, it means we found a matching pair- Calculate the number of consecutive cards needed:
i - last[x] + 1
- Update the minimum answer:
ans = min(ans, i - last[x] + 1)
- Calculate the number of consecutive cards needed:
- Update the hash table: Set
last[x] = i
to record the current position as the most recent occurrence of valuex
- Check if we've seen this value before: If
-
Return the result:
- If
ans == inf
, no matching pair was found, return-1
- Otherwise, return
ans
- If
Example walkthrough with cards = [3, 4, 2, 3, 4, 7]
:
i=0, x=3
:last = {3: 0}
, no match yeti=1, x=4
:last = {3: 0, 4: 1}
, no match yeti=2, x=2
:last = {3: 0, 4: 1, 2: 2}
, no match yeti=3, x=3
: Found3
inlast
! Distance =3 - 0 + 1 = 4
,ans = 4
, updatelast = {3: 3, 4: 1, 2: 2}
i=4, x=4
: Found4
inlast
! Distance =4 - 1 + 1 = 4
,ans = min(4, 4) = 4
, updatelast = {3: 3, 4: 4, 2: 2}
i=5, x=7
:last = {3: 3, 4: 4, 2: 2, 7: 5}
, no match
Final answer: 4
Time Complexity: O(n)
where n
is the length of the cards array - we make a single pass through the array.
Space Complexity: O(min(n, m))
where m
is the number of unique card values - in the worst case where all cards are unique, we store n
entries in the hash table.
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 a small example to illustrate the solution approach with cards = [7, 7, 3, 4, 7]
.
Initial Setup:
last = {}
(empty hash table)ans = infinity
Step-by-step traversal:
Step 1: Index 0, card value = 7
- Check if 7 is in
last
: No - Add to hash table:
last = {7: 0}
- No matching pair found yet
Step 2: Index 1, card value = 7
- Check if 7 is in
last
: Yes! (at index 0) - Calculate consecutive cards needed:
1 - 0 + 1 = 2
- Update minimum:
ans = min(infinity, 2) = 2
- Update hash table:
last = {7: 1}
Step 3: Index 2, card value = 3
- Check if 3 is in
last
: No - Add to hash table:
last = {7: 1, 3: 2}
- Current minimum remains 2
Step 4: Index 3, card value = 4
- Check if 4 is in
last
: No - Add to hash table:
last = {7: 1, 3: 2, 4: 3}
- Current minimum remains 2
Step 5: Index 4, card value = 7
- Check if 7 is in
last
: Yes! (at index 1) - Calculate consecutive cards needed:
4 - 1 + 1 = 4
- Update minimum:
ans = min(2, 4) = 2
- Update hash table:
last = {7: 4, 3: 2, 4: 3}
Result: The minimum number of consecutive cards needed is 2 (picking cards at indices 0 and 1, both with value 7).
This demonstrates how the algorithm efficiently finds the closest matching pair by tracking only the most recent position of each card value, avoiding the need to check all possible subarrays.
Solution Implementation
1from typing import List
2from math import inf
3
4
5class Solution:
6 def minimumCardPickup(self, cards: List[int]) -> int:
7 # Dictionary to store the last seen index of each card value
8 last_seen_index = {}
9
10 # Initialize minimum distance to infinity
11 min_distance = inf
12
13 # Iterate through the cards with their indices
14 for current_index, card_value in enumerate(cards):
15 # Check if we've seen this card value before
16 if card_value in last_seen_index:
17 # Calculate distance between current and last occurrence (inclusive)
18 distance = current_index - last_seen_index[card_value] + 1
19 # Update minimum distance if current distance is smaller
20 min_distance = min(min_distance, distance)
21
22 # Update the last seen index for this card value
23 last_seen_index[card_value] = current_index
24
25 # Return -1 if no matching pair found, otherwise return minimum distance
26 return -1 if min_distance == inf else min_distance
27
1class Solution {
2 public int minimumCardPickup(int[] cards) {
3 // HashMap to store the last seen index of each card value
4 Map<Integer, Integer> lastSeenIndex = new HashMap<>();
5
6 // Get the total number of cards
7 int totalCards = cards.length;
8
9 // Initialize minimum distance to impossible value (totalCards + 1)
10 // This helps identify if no matching pair was found
11 int minDistance = totalCards + 1;
12
13 // Iterate through all cards
14 for (int currentIndex = 0; currentIndex < totalCards; currentIndex++) {
15 int currentCard = cards[currentIndex];
16
17 // Check if we've seen this card value before
18 if (lastSeenIndex.containsKey(currentCard)) {
19 // Calculate distance between current and last occurrence
20 // Add 1 to include both cards in the count
21 int distance = currentIndex - lastSeenIndex.get(currentCard) + 1;
22
23 // Update minimum distance if current distance is smaller
24 minDistance = Math.min(minDistance, distance);
25 }
26
27 // Update the last seen index for current card value
28 lastSeenIndex.put(currentCard, currentIndex);
29 }
30
31 // If no matching pair was found, return -1; otherwise return minimum distance
32 return minDistance > totalCards ? -1 : minDistance;
33 }
34}
35
1class Solution {
2public:
3 int minimumCardPickup(vector<int>& cards) {
4 // Hash map to store the last seen index of each card value
5 unordered_map<int, int> lastSeenIndex;
6
7 int n = cards.size();
8 int minDistance = n + 1; // Initialize with impossible value (larger than array size)
9
10 // Iterate through all cards
11 for (int i = 0; i < n; ++i) {
12 // Check if current card value has been seen before
13 if (lastSeenIndex.count(cards[i])) {
14 // Calculate distance between current and last occurrence of same value
15 // Distance includes both endpoints, so we add 1
16 int currentDistance = i - lastSeenIndex[cards[i]] + 1;
17 minDistance = min(minDistance, currentDistance);
18 }
19
20 // Update the last seen index for current card value
21 lastSeenIndex[cards[i]] = i;
22 }
23
24 // If minDistance wasn't updated, no matching pair was found
25 return minDistance > n ? -1 : minDistance;
26 }
27};
28
1/**
2 * Finds the minimum number of consecutive cards to pick up to have a matching pair
3 * @param cards - Array of integers representing the cards
4 * @returns The minimum number of cards to pick up, or -1 if no matching pair exists
5 */
6function minimumCardPickup(cards: number[]): number {
7 const cardsLength: number = cards.length;
8
9 // Map to store the last seen index of each card value
10 const lastSeenIndex: Map<number, number> = new Map<number, number>();
11
12 // Initialize minimum distance to impossible value (length + 1)
13 let minimumDistance: number = cardsLength + 1;
14
15 // Iterate through each card
16 for (let currentIndex: number = 0; currentIndex < cardsLength; currentIndex++) {
17 const currentCard: number = cards[currentIndex];
18
19 // Check if we've seen this card value before
20 if (lastSeenIndex.has(currentCard)) {
21 // Calculate distance between current and previous occurrence
22 const previousIndex: number = lastSeenIndex.get(currentCard)!;
23 const distance: number = currentIndex - previousIndex + 1;
24
25 // Update minimum distance if current distance is smaller
26 minimumDistance = Math.min(minimumDistance, distance);
27 }
28
29 // Update the last seen index for current card value
30 lastSeenIndex.set(currentCard, currentIndex);
31 }
32
33 // Return -1 if no matching pair found, otherwise return minimum distance
34 return minimumDistance > cardsLength ? -1 : minimumDistance;
35}
36
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the array exactly once using a single for loop that processes each element. For each element, it performs constant time operations:
- Checking if a value exists in the dictionary:
O(1)
- Updating the minimum answer:
O(1)
- Updating the dictionary with the current index:
O(1)
Since we perform constant time operations for each of the n
elements in the array, the overall time complexity is O(n)
.
Space Complexity: O(n)
The space complexity is determined by the last
dictionary, which stores the most recent index for each unique value in the array. In the worst case, when all elements in the array are unique, the dictionary will store n
key-value pairs. Therefore, the space complexity is O(n)
.
The additional variables (ans
, i
, x
) use constant space O(1)
, which doesn't affect the overall space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-one error in distance calculation
The Pitfall:
A common mistake is calculating the distance between two matching cards as current_index - last_index
instead of current_index - last_index + 1
. This error occurs because we need to count the number of cards picked up inclusively from both ends.
Wrong Implementation:
# INCORRECT - misses the +1 distance = current_index - last_seen_index[card_value] # Wrong!
Why it's wrong:
If we have matching cards at indices 1 and 4, the consecutive cards we pick up are at indices [1, 2, 3, 4], which is 4 cards total. But 4 - 1 = 3
would give us only 3 cards.
Correct Implementation:
# CORRECT - includes both endpoints distance = current_index - last_seen_index[card_value] + 1
2. Not updating the hash table after finding a match
The Pitfall: Some might think that once a matching pair is found for a value, we don't need to update its position in the hash table anymore.
Wrong Implementation:
if card_value in last_seen_index:
distance = current_index - last_seen_index[card_value] + 1
min_distance = min(min_distance, distance)
# FORGOT to update last_seen_index here!
else:
last_seen_index[card_value] = current_index
Why it's wrong:
Consider cards = [3, 4, 3, 2, 3]
. If we don't update after finding the first pair of 3s (at indices 0 and 2), we'll miss the closer pair of 3s at indices 2 and 4, which would give us a smaller distance of 3 instead of 3.
Correct Implementation:
if card_value in last_seen_index:
distance = current_index - last_seen_index[card_value] + 1
min_distance = min(min_distance, distance)
# Always update the position, regardless of whether a match was found
last_seen_index[card_value] = current_index
3. Using 0 or a large constant instead of infinity for initialization
The Pitfall:
Initializing min_distance
with 0 or an arbitrary large number like 10000.
Wrong Implementation:
min_distance = 0 # Wrong - will always return 0 if no match found # or min_distance = 10000 # Wrong - might not be large enough for all cases
Why it's wrong:
- Using 0: The minimum distance will never update since all valid distances are positive
- Using arbitrary large numbers: May fail for arrays larger than your constant
Correct Implementation:
from math import inf min_distance = inf # Guarantees any valid distance will be smaller
Which data structure is used to implement recursion?
Recommended Readings
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
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
Want a Structured Path to Master System Design Too? Don’t Miss This!