3048. Earliest Second to Mark Indices I
Problem Description
You have two 1-indexed arrays: nums
of length n
and changeIndices
of length m
. Your goal is to mark all indices in nums
, which initially are all unmarked.
The process works through seconds from 1
to m
. At each second s
, you can perform exactly one of these operations:
- Decrement operation: Choose any index
i
in range[1, n]
and decreasenums[i]
by1
- Mark operation: If
nums[changeIndices[s]]
equals0
, you can mark the indexchangeIndices[s]
- Do nothing: Skip this second without performing any action
The key constraint is that you can only mark an index when two conditions are met:
- The value at that index in
nums
must be0
- The current second
s
must havechangeIndices[s]
pointing to that index
Your task is to find the earliest second (between 1
and m
) when it's possible to mark all indices in nums
by choosing operations optimally. If it's impossible to mark all indices within m
seconds, return -1
.
For example, if you want to mark index i
, you need to:
- First reduce
nums[i]
to0
through multiple decrement operations - Then wait for a second
s
wherechangeIndices[s] = i
to actually mark it
The challenge is to strategically choose when to decrement values and when to mark indices, considering that changeIndices
dictates which indices can be marked at each second.
Intuition
The first key observation is that if we can mark all indices within t
seconds, we can definitely mark them within any time t' > t
seconds. This monotonic property immediately suggests binary search on the answer - we search for the minimum time needed.
To understand why this works, consider what happens when we have more time available. With extra seconds, we can either perform more decrements to reduce values faster, or we have more opportunities where changeIndices[s]
points to indices we want to mark. More time never hurts our ability to complete the task.
For checking if a specific time t
is sufficient, we need to think strategically about when to mark each index. The crucial insight is that for each index i
, we should mark it at the last possible opportunity within the first t
seconds. Why? Because marking an index early doesn't give us any advantage - we still need to reduce its value to 0
first. By waiting until the last occurrence of index i
in changeIndices[:t]
, we maximize the time available to decrement its value.
This leads to our checking strategy:
- First, identify the last occurrence of each index within
changeIndices[:t]
using alast
dictionary - As we traverse through seconds
1
tot
, we make decisions:- If the current second is the last chance to mark index
i
(i.e.,last[i] == s
), we must have already decrementednums[i-1]
to0
. We check if we've accumulated enough decrement operations - If it's not the last chance for this index, we can use this second to decrement any value, adding to our decrement budget
- If the current second is the last chance to mark index
The decrement
variable acts like a budget - it tracks how many decrement operations we can perform. When we encounter the last chance to mark an index, we "spend" from this budget (the amount equals nums[i-1]
). If we don't have enough budget at any point, time t
is insufficient.
This greedy approach of marking at the last possible moment ensures we have maximum flexibility in scheduling our decrement operations, making it the optimal strategy for checking feasibility.
Learn more about Binary Search patterns.
Solution Approach
The solution uses binary search combined with a greedy checking function to find the minimum time needed.
Binary Search Setup:
- We search in the range
[1, m+1]
wherem = len(changeIndices)
- Using
bisect_left
with a custom key function that returnsTrue
if timet
is sufficient - The result needs
+1
adjustment becausebisect_left
returns 0-indexed position
The Check Function Implementation:
For a given time t
, we need to verify if all indices can be marked within t
seconds:
-
Build the
last
dictionary:last = {i: s for s, i in enumerate(changeIndices[:t])}
This maps each index to its last occurrence position within the first
t
elements ofchangeIndices
. We use 0-based indexing here for enumeration. -
Traverse through the first
t
seconds:for s, i in enumerate(changeIndices[:t]):
For each second
s
and its corresponding indexi
: -
Decision at each second:
- If
last[i] == s
(this is the last chance to mark indexi
):- Check if we have enough decrements:
if decrement < nums[i - 1]: return False
- If sufficient, consume the decrements:
decrement -= nums[i - 1]
- Mark this index:
marked += 1
- Check if we have enough decrements:
- Otherwise (not the last chance for this index):
- Add this second to our decrement budget:
decrement += 1
- We can use this second later for any decrement operation
- Add this second to our decrement budget:
- If
-
Final validation:
return marked == len(nums)
Check if we successfully marked all
n
indices.
Why this greedy approach works:
- By marking each index at its last possible occurrence, we maximize the time available for accumulating decrements
- The
decrement
counter tracks our "budget" - seconds we can use for decrementing values - When we must mark an index (at its last occurrence), we verify we've accumulated enough decrements to have reduced that index's value to 0
- If at any point we don't have enough decrements, the time
t
is insufficient
Time Complexity:
- Binary search runs
O(log m)
iterations - Each check function runs in
O(t)
time wheret ≤ m
- Overall:
O(m log m)
Space Complexity: O(n)
for the last
dictionary storing at most n
unique indices.
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 concrete example to illustrate the solution approach.
Input:
nums = [3, 2, 3]
(1-indexed, so index 1 has value 3, index 2 has value 2, index 3 has value 3)changeIndices = [1, 3, 2, 2, 2, 2, 3]
(length m = 7)
Goal: Find the earliest second when all three indices can be marked.
Step 1: Binary Search Setup
We'll search for the minimum time in range [1, 8). Let's check if time t = 4
is sufficient.
Step 2: Check if t = 4 works
First, we look at changeIndices[:4] = [1, 3, 2, 2]
and build the last
dictionary:
- Index 1 appears at position 0 (last occurrence)
- Index 3 appears at position 1 (last occurrence)
- Index 2 appears at positions 2 and 3 (last at position 3)
So last = {1: 0, 3: 1, 2: 3}
Now we traverse second by second:
Second 0 (changeIndices[0] = 1):
- This is the last chance to mark index 1 (last[1] = 0)
- We need
nums[0] = 3
decrements to reduce it to 0 - Current
decrement = 0
, but we need 3 - Return False - time 4 is insufficient!
Step 3: Check if t = 7 works
Looking at changeIndices[:7] = [1, 3, 2, 2, 2, 2, 3]
:
- Build
last = {1: 0, 3: 6, 2: 5}
Traverse second by second:
Second 0 (changeIndices[0] = 1):
- Last chance for index 1 (last[1] = 0)
- Need 3 decrements, have 0
- Return False - Still insufficient!
Actually, let's reconsider with a better strategy. Since index 1 must be marked at second 0, we need to have already decremented it to 0 before we start. This is impossible.
Step 4: Reanalyze the problem
Looking more carefully at the full changeIndices = [1, 3, 2, 2, 2, 2, 3]
:
- We need at least 3 + 2 + 3 = 8 total operations (decrements) plus 3 marks
- That's 11 operations minimum, but we only have 7 seconds
- Return -1 - It's impossible!
Let's try a simpler example where it's actually possible:
Modified Example:
nums = [2, 1, 1]
changeIndices = [1, 2, 3, 2, 1, 3]
Check if t = 6 works:
Build last = {1: 4, 2: 3, 3: 5}
(using 0-based indexing)
Second 0 (index 1): Not last occurrence, decrement = 1
Second 1 (index 2): Not last occurrence, decrement = 2
Second 2 (index 3): Not last occurrence, decrement = 3
Second 3 (index 2): Last chance for index 2!
- Need
nums[1] = 1
decrement - Have 3 decrements available
- Use 1, mark index 2:
decrement = 2
,marked = 1
Second 4 (index 1): Last chance for index 1!
- Need
nums[0] = 2
decrements - Have 2 decrements available
- Use 2, mark index 1:
decrement = 0
,marked = 2
Second 5 (index 3): Last chance for index 3!
- Need
nums[2] = 1
decrement - Have 0 decrements available
- Return False - Not enough!
Since t = 6 fails, binary search would try larger values. Eventually it would find that we need more time to accumulate enough decrements before marking all indices.
Solution Implementation
1class Solution:
2 def earliestSecondToMarkIndices(
3 self, nums: List[int], changeIndices: List[int]
4 ) -> int:
5 """
6 Find the earliest second when all indices can be marked.
7
8 Args:
9 nums: List of values at each index (1-indexed in changeIndices)
10 changeIndices: List indicating which index can be changed at each second
11
12 Returns:
13 The earliest second to mark all indices, or -1 if impossible
14 """
15
16 def can_mark_all_by_second(seconds: int) -> bool:
17 """
18 Check if all indices can be marked within the given number of seconds.
19
20 Strategy:
21 - Track available decrement operations
22 - For each index, we can only mark it at its last occurrence
23 - Before marking, we need to decrement its value to 0
24 """
25 available_decrements = 0
26 marked_count = 0
27
28 # Find the last occurrence of each index within the time limit
29 # changeIndices uses 1-based indexing
30 last_occurrence = {}
31 for second, index in enumerate(changeIndices[:seconds]):
32 last_occurrence[index] = second
33
34 # Process each second in order
35 for current_second, current_index in enumerate(changeIndices[:seconds]):
36 # Check if this is the last chance to mark this index
37 if last_occurrence[current_index] == current_second:
38 # Need to have decremented nums[current_index - 1] to 0
39 # (converting from 1-based to 0-based indexing)
40 required_decrements = nums[current_index - 1]
41
42 if available_decrements < required_decrements:
43 # Not enough decrements available, can't mark this index
44 return False
45
46 # Use decrements and mark this index
47 available_decrements -= required_decrements
48 marked_count += 1
49 else:
50 # This second can be used for a decrement operation
51 available_decrements += 1
52
53 # Check if all indices were successfully marked
54 return marked_count == len(nums)
55
56 # Binary search for the minimum seconds needed
57 total_seconds = len(changeIndices)
58
59 # Search in range [1, total_seconds + 1] for the first valid second count
60 # Using bisect_left with key function to find first True value
61 minimum_seconds = bisect_left(
62 range(1, total_seconds + 2),
63 True,
64 key=can_mark_all_by_second
65 ) + 1
66
67 # Return -1 if no valid solution exists within available seconds
68 return -1 if minimum_seconds > total_seconds else minimum_seconds
69
1class Solution {
2 private int[] nums;
3 private int[] changeIndices;
4
5 /**
6 * Finds the earliest second at which all indices can be marked.
7 * Uses binary search to find the minimum time needed.
8 *
9 * @param nums Array where nums[i] represents the value that must be decremented to 0 before marking index i+1
10 * @param changeIndices Array where changeIndices[s] represents which index can be marked at second s+1
11 * @return The earliest second to mark all indices, or -1 if impossible
12 */
13 public int earliestSecondToMarkIndices(int[] nums, int[] changeIndices) {
14 this.nums = nums;
15 this.changeIndices = changeIndices;
16
17 int totalSeconds = changeIndices.length;
18
19 // Binary search on the answer: minimum seconds needed
20 int left = 1;
21 int right = totalSeconds + 1;
22
23 while (left < right) {
24 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
25
26 if (check(mid)) {
27 // If we can mark all indices in 'mid' seconds, try fewer seconds
28 right = mid;
29 } else {
30 // If we cannot mark all indices in 'mid' seconds, we need more time
31 left = mid + 1;
32 }
33 }
34
35 // If left exceeds totalSeconds, it's impossible to mark all indices
36 return left > totalSeconds ? -1 : left;
37 }
38
39 /**
40 * Checks if all indices can be marked within the given time limit.
41 *
42 * @param timeLimit The number of seconds available
43 * @return true if all indices can be marked within timeLimit seconds, false otherwise
44 */
45 private boolean check(int timeLimit) {
46 // Track the last occurrence of each index within the time limit
47 // last[i] stores the last second when index i appears in changeIndices
48 int[] lastOccurrence = new int[nums.length + 1];
49
50 for (int second = 0; second < timeLimit; ++second) {
51 lastOccurrence[changeIndices[second]] = second;
52 }
53
54 // Track available decrement operations and successfully marked indices
55 int availableDecrements = 0;
56 int markedCount = 0;
57
58 // Process each second up to the time limit
59 for (int second = 0; second < timeLimit; ++second) {
60 int currentIndex = changeIndices[second];
61
62 // Check if this is the last chance to mark this index
63 if (lastOccurrence[currentIndex] == second) {
64 // We must mark this index now (last opportunity)
65 // Check if we have enough decrements to reduce nums[currentIndex-1] to 0
66 if (availableDecrements < nums[currentIndex - 1]) {
67 // Not enough decrements accumulated, cannot mark this index
68 return false;
69 }
70
71 // Use the required decrements and mark the index
72 availableDecrements -= nums[currentIndex - 1];
73 ++markedCount;
74 } else {
75 // This index appears again later, so we can use this second for decrementing
76 ++availableDecrements;
77 }
78 }
79
80 // Check if all indices have been marked
81 return markedCount == nums.length;
82 }
83}
84
1class Solution {
2public:
3 int earliestSecondToMarkIndices(vector<int>& nums, vector<int>& changeIndices) {
4 int n = nums.size();
5
6 // Lambda function to check if we can mark all indices within 't' seconds
7 auto canMarkAllIndices = [&](int secondsLimit) {
8 // Track the last occurrence of each index in changeIndices within the time limit
9 int lastOccurrence[n + 1];
10 memset(lastOccurrence, 0, sizeof(lastOccurrence));
11
12 // Find the last second when each index appears
13 for (int second = 0; second < secondsLimit; ++second) {
14 lastOccurrence[changeIndices[second]] = second;
15 }
16
17 // Track available operations for decrementing and number of marked indices
18 int availableDecrements = 0;
19 int markedCount = 0;
20
21 // Process each second in order
22 for (int second = 0; second < secondsLimit; ++second) {
23 int currentIndex = changeIndices[second];
24
25 // Check if this is the last occurrence of this index
26 if (lastOccurrence[currentIndex] == second) {
27 // We must mark this index now (last chance)
28 // Check if we have enough decrements to reduce nums[i-1] to 0
29 if (availableDecrements < nums[currentIndex - 1]) {
30 return false; // Not enough operations to prepare this index
31 }
32
33 // Use the required decrements and mark the index
34 availableDecrements -= nums[currentIndex - 1];
35 ++markedCount;
36 } else {
37 // This index will appear again, so we can use this second for decrementing
38 ++availableDecrements;
39 }
40 }
41
42 // Check if all indices have been marked
43 return markedCount == n;
44 };
45
46 // Binary search for the minimum number of seconds needed
47 int totalSeconds = changeIndices.size();
48 int left = 1;
49 int right = totalSeconds + 1;
50
51 while (left < right) {
52 int mid = (left + right) >> 1;
53
54 if (canMarkAllIndices(mid)) {
55 right = mid; // Try to find an earlier time
56 } else {
57 left = mid + 1; // Need more time
58 }
59 }
60
61 // Return -1 if impossible, otherwise return the minimum seconds needed
62 return left > totalSeconds ? -1 : left;
63 }
64};
65
1/**
2 * Finds the earliest second at which all indices can be marked.
3 * Uses binary search to find the minimum time needed.
4 *
5 * @param nums - Array where nums[i] represents the value at index i+1
6 * @param changeIndices - Array where changeIndices[s] represents the index that can be changed at second s+1
7 * @returns The earliest second to mark all indices, or -1 if impossible
8 */
9function earliestSecondToMarkIndices(nums: number[], changeIndices: number[]): number {
10 const n: number = nums.length;
11 const m: number = changeIndices.length;
12
13 // Binary search bounds: minimum 1 second, maximum m+1 seconds
14 let left: number = 1;
15 let right: number = m + 1;
16
17 /**
18 * Checks if all indices can be marked within the given time limit.
19 *
20 * @param timeLimit - The number of seconds available
21 * @returns true if all indices can be marked within timeLimit seconds
22 */
23 const canMarkAllIndices = (timeLimit: number): boolean => {
24 // Track the last occurrence of each index in changeIndices within timeLimit
25 const lastOccurrence: number[] = Array(n + 1).fill(0);
26
27 // Find the last second each index appears
28 for (let second = 0; second < timeLimit; second++) {
29 lastOccurrence[changeIndices[second]] = second;
30 }
31
32 // Track available decrements and number of marked indices
33 let availableDecrements: number = 0;
34 let markedCount: number = 0;
35
36 // Process each second up to timeLimit
37 for (let second = 0; second < timeLimit; second++) {
38 const currentIndex: number = changeIndices[second];
39
40 // If this is the last occurrence of this index, try to mark it
41 if (lastOccurrence[currentIndex] === second) {
42 // Check if we have enough decrements to reduce the value to 0
43 if (availableDecrements < nums[currentIndex - 1]) {
44 return false; // Not enough decrements available
45 }
46
47 // Use decrements to reduce value to 0 and mark the index
48 availableDecrements -= nums[currentIndex - 1];
49 markedCount++;
50 } else {
51 // This second can be used for a decrement operation
52 availableDecrements++;
53 }
54 }
55
56 // Check if all indices have been marked
57 return markedCount === n;
58 };
59
60 // Binary search for the minimum time needed
61 while (left < right) {
62 const mid: number = (left + right) >> 1; // Equivalent to Math.floor((left + right) / 2)
63
64 if (canMarkAllIndices(mid)) {
65 // If possible with mid seconds, try fewer seconds
66 right = mid;
67 } else {
68 // If not possible with mid seconds, need more seconds
69 left = mid + 1;
70 }
71 }
72
73 // Return the result: -1 if impossible, otherwise the minimum seconds needed
74 return left > m ? -1 : left;
75}
76
Time and Space Complexity
Time Complexity: O(m × log m)
The algorithm uses binary search on the range [1, m+1]
where m
is the length of changeIndices
. The binary search performs O(log m)
iterations. For each iteration, the check
function is called, which:
- Creates a dictionary
last
by iterating throughchangeIndices[:t]
, takingO(t)
time wheret ≤ m
- Iterates through
changeIndices[:t]
once more to validate the marking process, takingO(t)
time
Since t
can be at most m
, each call to check
takes O(m)
time. Therefore, the total time complexity is O(m × log m)
.
Space Complexity: O(n)
The space complexity comes from:
- The
last
dictionary which stores at mostn
key-value pairs (wheren
is the length ofnums
), since there aren
unique indices that can be marked - The local variables
decrement
andmarked
useO(1)
space
Therefore, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Index Confusion Between 0-based and 1-based Systems
The most common pitfall in this problem is mixing up the indexing systems:
nums
array uses 0-based indexing in PythonchangeIndices
contains 1-based index values- The enumeration in the loop uses 0-based indexing
Pitfall Example:
# WRONG: Forgetting to convert from 1-based to 0-based required_decrements = nums[current_index] # Should be nums[current_index - 1]
Solution:
Always remember to subtract 1 when accessing nums
array using indices from changeIndices
:
required_decrements = nums[current_index - 1] # Correct conversion
2. Incorrect Binary Search Range
Pitfall Example:
# WRONG: Using 0-based range
minimum_seconds = bisect_left(range(0, total_seconds), True, key=can_mark_all_by_second)
This would search starting from 0 seconds, which doesn't make sense since we need at least 1 second.
Solution: Use the correct 1-based range and adjust the result:
minimum_seconds = bisect_left(
range(1, total_seconds + 2),
True,
key=can_mark_all_by_second
) + 1
3. Not Handling Indices That Never Appear
Pitfall Example:
If an index from nums
never appears in changeIndices[:seconds]
, it won't be in the last_occurrence
dictionary, leading to a KeyError when checking last_occurrence[current_index]
.
Solution: First verify that all indices appear at least once:
def can_mark_all_by_second(seconds: int) -> bool:
last_occurrence = {}
for second, index in enumerate(changeIndices[:seconds]):
last_occurrence[index] = second
# Check if all indices appear at least once
if len(last_occurrence) < len(nums):
return False # Some indices never appear, impossible to mark all
# Continue with the rest of the logic...
4. Off-by-One Error in Binary Search Result
Pitfall Example:
# WRONG: Forgetting to add 1 to bisect_left result
minimum_seconds = bisect_left(range(1, total_seconds + 2), True, key=can_mark_all_by_second)
Since bisect_left
returns a 0-based index position but we're searching in a 1-based range, we need to adjust.
Solution: Add 1 to convert from position to actual second value:
minimum_seconds = bisect_left(...) + 1
5. Incorrect Greedy Logic for Accumulating Decrements
Pitfall Example:
# WRONG: Accumulating decrements even when marking if last_occurrence[current_index] == current_second: available_decrements -= nums[current_index - 1] available_decrements += 1 # WRONG: shouldn't add here marked_count += 1
Solution: Only accumulate decrements when NOT marking (using the second for other operations):
if last_occurrence[current_index] == current_second: available_decrements -= nums[current_index - 1] marked_count += 1 else: available_decrements += 1 # Only add when not marking
Which of the following shows the order of node visit in a Breadth-first Search?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!