Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
You are given two 1-indexed integer arrays, nums and, changeIndices, having lengths n and m, respectively.
Initially, all indices in nums are unmarked. Your task is to mark all indices in nums.
In each second, s, in order from 1 to m (inclusive), you can perform one of the following operations:
i in the range [1, n] and decrement nums[i] by 1.nums[changeIndices[s]] to any non-negative value.i in the range [1, n], where nums[i] is equal to 0, and mark index i.Return an integer denoting the earliest second in the range [1, m] when all indices in nums can be marked by choosing operations optimally, or -1 if it is impossible.
Example 1:
Input: nums = [3,2,3], changeIndices = [1,3,2,2,2,2,3] Output: 6 Explanation: In this example, we have 7 seconds. The following operations can be performed to mark all indices: Second 1: Set nums[changeIndices[1]] to 0. nums becomes [0,2,3]. Second 2: Set nums[changeIndices[2]] to 0. nums becomes [0,2,0]. Second 3: Set nums[changeIndices[3]] to 0. nums becomes [0,0,0]. Second 4: Mark index 1, since nums[1] is equal to 0. Second 5: Mark index 2, since nums[2] is equal to 0. Second 6: Mark index 3, since nums[3] is equal to 0. Now all indices have been marked. It can be shown that it is not possible to mark all indices earlier than the 6th second. Hence, the answer is 6.
Example 2:
Input: nums = [0,0,1,2], changeIndices = [1,2,1,2,1,2,1,2] Output: 7 Explanation: In this example, we have 8 seconds. The following operations can be performed to mark all indices: Second 1: Mark index 1, since nums[1] is equal to 0. Second 2: Mark index 2, since nums[2] is equal to 0. Second 3: Decrement index 4 by one. nums becomes [0,0,1,1]. Second 4: Decrement index 4 by one. nums becomes [0,0,1,0]. Second 5: Decrement index 3 by one. nums becomes [0,0,0,0]. Second 6: Mark index 3, since nums[3] is equal to 0. Second 7: Mark index 4, since nums[4] is equal to 0. Now all indices have been marked. It can be shown that it is not possible to mark all indices earlier than the 7th second. Hence, the answer is 7.
Example 3:
Input: nums = [1,2,3], changeIndices = [1,2,3] Output: -1 Explanation: In this example, it can be shown that it is impossible to mark all indices, as we don't have enough seconds. Hence, the answer is -1.
Constraints:
1 <= n == nums.length <= 50000 <= nums[i] <= 1091 <= m == changeIndices.length <= 50001 <= changeIndices[i] <= nProblem summary: You are given two 1-indexed integer arrays, nums and, changeIndices, having lengths n and m, respectively. Initially, all indices in nums are unmarked. Your task is to mark all indices in nums. In each second, s, in order from 1 to m (inclusive), you can perform one of the following operations: Choose an index i in the range [1, n] and decrement nums[i] by 1. Set nums[changeIndices[s]] to any non-negative value. Choose an index i in the range [1, n], where nums[i] is equal to 0, and mark index i. Do nothing. Return an integer denoting the earliest second in the range [1, m] when all indices in nums can be marked by choosing operations optimally, or -1 if it is impossible.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Binary Search · Greedy
[3,2,3] [1,3,2,2,2,2,3]
[0,0,1,2] [1,2,1,2,1,2,1,2]
[1,2,3] [1,2,3]
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #3049: Earliest Second to Mark Indices II
class Solution {
public int earliestSecondToMarkIndices(int[] nums, int[] changeIndices) {
final long numsSum = Arrays.stream(nums).asLongStream().sum();
// {the second: the index of nums can be zeroed at the current second}
Map<Integer, Integer> secondToIndex = getSecondToIndex(nums, changeIndices);
int l = 0;
int r = changeIndices.length + 1;
while (l < r) {
final int m = (l + r) / 2;
if (canMark(nums, secondToIndex, m))
r = m;
else
l = m + 1;
}
return l <= changeIndices.length ? l : -1;
}
// Returns true if all indices of `nums` can be marked within `maxSecond`.
private boolean canMark(int[] nums, Map<Integer, Integer> secondToIndex, int maxSecond,
final long numsSum) {
// Use a min-heap to greedily pop out the minimum number, which yields the
// least saving.
Queue<Integer> minHeap = new PriorityQueue<>();
int marks = 0;
for (int second = maxSecond - 1; second >= 0; --second) {
if (secondToIndex.containsKey(second)) {
// The number mapped by the index is a candidate to be zeroed out.
final int index = secondToIndex.get(second);
minHeap.offer(nums[index]);
if (marks == 0) {
// Running out of marks, so need to pop out the minimum number.
// So, the current second will be used to mark an index.
minHeap.poll();
++marks;
} else {
// There're enough marks.
// So, the current second will be used to zero out a number.
--marks;
}
} else {
// There's no candidate to be zeroed out.
// So, the current second will be used to mark an index.
++marks;
}
}
final int heapSize = minHeap.size();
final long decrementAndMarkCost = numsSum - getHeapSum(minHeap) + (nums.length - heapSize);
final long zeroAndMarkCost = heapSize + heapSize;
return decrementAndMarkCost + zeroAndMarkCost <= maxSecond;
}
private long getHeapSum(Queue<Integer> minHeap) {
long sum = 0;
while (!minHeap.isEmpty())
sum += minHeap.poll();
return sum;
}
private Map<Integer, Integer> getSecondToIndex(int[] nums, int[] changeIndices) {
// {the `index` of nums: the earliest second to zero out nums[index]}
Map<Integer, Integer> indexToFirstSecond = new HashMap<>();
Map<Integer, Integer> secondToIndex = new HashMap<>();
for (int zeroIndexedSecond = 0; zeroIndexedSecond < changeIndices.length; ++zeroIndexedSecond) {
// Convert to 0-indexed.
final int index = changeIndices[zeroIndexedSecond] - 1;
if (nums[index] > 0)
indexToFirstSecond.putIfAbsent(index, zeroIndexedSecond);
}
for (Map.Entry<Integer, Integer> entry : indexToFirstSecond.entrySet()) {
final int index = entry.getKey();
final int second = entry.getValue();
secondToIndex.put(second, index);
}
return secondToIndex;
}
}
// Accepted solution for LeetCode #3049: Earliest Second to Mark Indices II
package main
import (
"container/heap"
"sort"
)
// https://space.bilibili.com/206214
func earliestSecondToMarkIndices(nums, changeIndices []int) int {
n, m := len(nums), len(changeIndices)
if n > m {
return -1
}
total := n
for _, v := range nums {
total += v
}
firstT := make([]int, n)
for t := m - 1; t >= 0; t-- {
firstT[changeIndices[t]-1] = t + 1
}
h := hp{}
ans := n + sort.Search(m+1-n, func(mx int) bool {
mx += n
cnt, slow := 0, total
h.IntSlice = h.IntSlice[:0]
for t := mx - 1; t >= 0; t-- {
i := changeIndices[t] - 1
v := nums[i]
if v <= 1 || t != firstT[i]-1 {
cnt++ // 留给左边,用来快速复习/考试
continue
}
if cnt == 0 {
if h.Len() == 0 || v <= h.IntSlice[0] {
cnt++ // 留给左边,用来快速复习/考试
continue
}
slow += heap.Pop(&h).(int) + 1
cnt += 2 // 反悔:一天快速复习,一天考试
}
slow -= v + 1
cnt-- // 快速复习,然后消耗一天来考试
heap.Push(&h, v)
}
return cnt >= slow // 剩余天数不能慢速复习+考试
})
if ans > m {
return -1
}
return ans
}
type hp struct{ sort.IntSlice }
func (h *hp) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) }
func (h *hp) Pop() any { a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v }
# Accepted solution for LeetCode #3049: Earliest Second to Mark Indices II
class Solution:
def earliestSecondToMarkIndices(
self,
nums: list[int],
changeIndices: list[int],
) -> int:
# {the second: the index of nums can be zeroed at the current second}
secondToIndex = self._getSecondToIndex(nums, changeIndices)
numsSum = sum(nums)
def canMark(maxSecond: int) -> bool:
"""
Returns True if all indices of `nums` can be marked within `maxSecond`.
"""
# Use a min-heap to greedily pop out the minimum number, which yields the
# least saving.
minHeap = []
marks = 0
for second in range(maxSecond - 1, -1, -1):
if second in secondToIndex:
# The number mapped by the index is a candidate to be zeroed out.
index = secondToIndex[second]
heapq.heappush(minHeap, nums[index])
if marks == 0:
# Running out of marks, so need to pop out the minimum number.
# So, the current second will be used to mark an index.
heapq.heappop(minHeap)
marks += 1
else:
# There're enough marks.
# So, the current second will be used to zero out a number.
marks -= 1
else:
# There's no candidate to be zeroed out.
# So, the current second will be used to mark an index.
marks += 1
decrementAndMarkCost = ((numsSum - sum(minHeap)) +
(len(nums) - len(minHeap)))
zeroAndMarkCost = len(minHeap) + len(minHeap)
return decrementAndMarkCost + zeroAndMarkCost <= maxSecond
l = 0
r = len(changeIndices) + 1
ans = bisect.bisect_left(range(l, r), True, key=canMark) + l
return ans if ans <= len(changeIndices) else -1
def _getSecondToIndex(
self,
nums: list[int],
changeIndices: list[int],
) -> dict[int, int]:
# {the `index` of nums: the earliest second to zero out nums[index]}
indexToFirstSecond = {}
for zeroIndexedSecond, oneIndexedIndex in enumerate(changeIndices):
index = oneIndexedIndex - 1 # Convert to 0-indexed.
if nums[index] > 0 and index not in indexToFirstSecond:
indexToFirstSecond[index] = zeroIndexedSecond
return {second: index for index, second in indexToFirstSecond.items()}
// Accepted solution for LeetCode #3049: Earliest Second to Mark Indices II
// Rust example auto-generated from java reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (java):
// // Accepted solution for LeetCode #3049: Earliest Second to Mark Indices II
// class Solution {
// public int earliestSecondToMarkIndices(int[] nums, int[] changeIndices) {
// final long numsSum = Arrays.stream(nums).asLongStream().sum();
// // {the second: the index of nums can be zeroed at the current second}
// Map<Integer, Integer> secondToIndex = getSecondToIndex(nums, changeIndices);
// int l = 0;
// int r = changeIndices.length + 1;
//
// while (l < r) {
// final int m = (l + r) / 2;
// if (canMark(nums, secondToIndex, m))
// r = m;
// else
// l = m + 1;
// }
//
// return l <= changeIndices.length ? l : -1;
// }
//
// // Returns true if all indices of `nums` can be marked within `maxSecond`.
// private boolean canMark(int[] nums, Map<Integer, Integer> secondToIndex, int maxSecond,
// final long numsSum) {
// // Use a min-heap to greedily pop out the minimum number, which yields the
// // least saving.
// Queue<Integer> minHeap = new PriorityQueue<>();
// int marks = 0;
//
// for (int second = maxSecond - 1; second >= 0; --second) {
// if (secondToIndex.containsKey(second)) {
// // The number mapped by the index is a candidate to be zeroed out.
// final int index = secondToIndex.get(second);
// minHeap.offer(nums[index]);
// if (marks == 0) {
// // Running out of marks, so need to pop out the minimum number.
// // So, the current second will be used to mark an index.
// minHeap.poll();
// ++marks;
// } else {
// // There're enough marks.
// // So, the current second will be used to zero out a number.
// --marks;
// }
// } else {
// // There's no candidate to be zeroed out.
// // So, the current second will be used to mark an index.
// ++marks;
// }
// }
//
// final int heapSize = minHeap.size();
// final long decrementAndMarkCost = numsSum - getHeapSum(minHeap) + (nums.length - heapSize);
// final long zeroAndMarkCost = heapSize + heapSize;
// return decrementAndMarkCost + zeroAndMarkCost <= maxSecond;
// }
//
// private long getHeapSum(Queue<Integer> minHeap) {
// long sum = 0;
// while (!minHeap.isEmpty())
// sum += minHeap.poll();
// return sum;
// }
//
// private Map<Integer, Integer> getSecondToIndex(int[] nums, int[] changeIndices) {
// // {the `index` of nums: the earliest second to zero out nums[index]}
// Map<Integer, Integer> indexToFirstSecond = new HashMap<>();
// Map<Integer, Integer> secondToIndex = new HashMap<>();
// for (int zeroIndexedSecond = 0; zeroIndexedSecond < changeIndices.length; ++zeroIndexedSecond) {
// // Convert to 0-indexed.
// final int index = changeIndices[zeroIndexedSecond] - 1;
// if (nums[index] > 0)
// indexToFirstSecond.putIfAbsent(index, zeroIndexedSecond);
// }
// for (Map.Entry<Integer, Integer> entry : indexToFirstSecond.entrySet()) {
// final int index = entry.getKey();
// final int second = entry.getValue();
// secondToIndex.put(second, index);
// }
// return secondToIndex;
// }
// }
// Accepted solution for LeetCode #3049: Earliest Second to Mark Indices II
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #3049: Earliest Second to Mark Indices II
// class Solution {
// public int earliestSecondToMarkIndices(int[] nums, int[] changeIndices) {
// final long numsSum = Arrays.stream(nums).asLongStream().sum();
// // {the second: the index of nums can be zeroed at the current second}
// Map<Integer, Integer> secondToIndex = getSecondToIndex(nums, changeIndices);
// int l = 0;
// int r = changeIndices.length + 1;
//
// while (l < r) {
// final int m = (l + r) / 2;
// if (canMark(nums, secondToIndex, m))
// r = m;
// else
// l = m + 1;
// }
//
// return l <= changeIndices.length ? l : -1;
// }
//
// // Returns true if all indices of `nums` can be marked within `maxSecond`.
// private boolean canMark(int[] nums, Map<Integer, Integer> secondToIndex, int maxSecond,
// final long numsSum) {
// // Use a min-heap to greedily pop out the minimum number, which yields the
// // least saving.
// Queue<Integer> minHeap = new PriorityQueue<>();
// int marks = 0;
//
// for (int second = maxSecond - 1; second >= 0; --second) {
// if (secondToIndex.containsKey(second)) {
// // The number mapped by the index is a candidate to be zeroed out.
// final int index = secondToIndex.get(second);
// minHeap.offer(nums[index]);
// if (marks == 0) {
// // Running out of marks, so need to pop out the minimum number.
// // So, the current second will be used to mark an index.
// minHeap.poll();
// ++marks;
// } else {
// // There're enough marks.
// // So, the current second will be used to zero out a number.
// --marks;
// }
// } else {
// // There's no candidate to be zeroed out.
// // So, the current second will be used to mark an index.
// ++marks;
// }
// }
//
// final int heapSize = minHeap.size();
// final long decrementAndMarkCost = numsSum - getHeapSum(minHeap) + (nums.length - heapSize);
// final long zeroAndMarkCost = heapSize + heapSize;
// return decrementAndMarkCost + zeroAndMarkCost <= maxSecond;
// }
//
// private long getHeapSum(Queue<Integer> minHeap) {
// long sum = 0;
// while (!minHeap.isEmpty())
// sum += minHeap.poll();
// return sum;
// }
//
// private Map<Integer, Integer> getSecondToIndex(int[] nums, int[] changeIndices) {
// // {the `index` of nums: the earliest second to zero out nums[index]}
// Map<Integer, Integer> indexToFirstSecond = new HashMap<>();
// Map<Integer, Integer> secondToIndex = new HashMap<>();
// for (int zeroIndexedSecond = 0; zeroIndexedSecond < changeIndices.length; ++zeroIndexedSecond) {
// // Convert to 0-indexed.
// final int index = changeIndices[zeroIndexedSecond] - 1;
// if (nums[index] > 0)
// indexToFirstSecond.putIfAbsent(index, zeroIndexedSecond);
// }
// for (Map.Entry<Integer, Integer> entry : indexToFirstSecond.entrySet()) {
// final int index = entry.getKey();
// final int second = entry.getValue();
// secondToIndex.put(second, index);
// }
// return secondToIndex;
// }
// }
Use this to step through a reusable interview workflow for this problem.
Check every element from left to right until we find the target or exhaust the array. Each comparison is O(1), and we may visit all n elements, giving O(n). No extra space needed.
Each comparison eliminates half the remaining search space. After k comparisons, the space is n/2ᵏ. We stop when the space is 1, so k = log₂ n. No extra memory needed — just two pointers (lo, hi).
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Wrong move: Setting `lo = mid` or `hi = mid` can stall and create an infinite loop.
Usually fails on: Two-element ranges never converge.
Fix: Use `lo = mid + 1` or `hi = mid - 1` where appropriate.
Wrong move: Locally optimal choices may fail globally.
Usually fails on: Counterexamples appear on crafted input orderings.
Fix: Verify with exchange argument or monotonic objective before committing.