3080. Mark Elements on Array by Performing Queries
Problem Description
You have an array nums
of positive integers (0-indexed) and need to process a series of queries on it. Each element in the array starts as "unmarked".
For each query queries[i] = [index_i, k_i]
, you perform two steps:
- Mark the element at position
index_i
(if it's not already marked) - Mark
k_i
additional unmarked elements that have the smallest values in the array- If multiple elements have the same smallest value, mark those with smaller indices first
- If there are fewer than
k_i
unmarked elements remaining, mark all of them
After each query, you need to calculate the sum of all unmarked elements.
Example walkthrough:
If nums = [1, 2, 2, 1, 2, 3, 1]
and queries = [[1, 2], [3, 3]]
:
- Initially all elements are unmarked, sum = 12
- Query 1:
[1, 2]
- Mark element at index 1 (value = 2)
- Mark 2 smallest unmarked elements: indices 0 and 3 (both have value 1)
- Unmarked elements: indices 2, 4, 5, 6 with sum = 2 + 2 + 3 + 1 = 8
- Query 2:
[3, 3]
- Element at index 3 already marked, skip
- Mark 3 smallest unmarked elements: index 6 (value 1), indices 2 and 4 (both value 2)
- Unmarked element: only index 5 with sum = 3
Return [8, 3]
as the answer array containing the sum of unmarked elements after each query.
Intuition
The key insight is that we need to efficiently find and mark the smallest unmarked elements repeatedly across multiple queries. If we try to search for the smallest unmarked elements every time we process a query, it would be inefficient.
Instead, we can preprocess the array by sorting all elements by their values (keeping track of their original indices). This gives us a global ordering of elements from smallest to largest. When we need to mark k
smallest unmarked elements, we can simply traverse this sorted list from where we left off in the previous query.
Think of it like having a queue of people sorted by height. When asked to select the k
shortest people who haven't been selected yet, we just go down the line from where we stopped last time, skipping anyone already selected, until we've picked k
people.
The running sum optimization is straightforward - instead of recalculating the sum after each query, we maintain a running sum s
that starts as the total sum of all elements. Whenever we mark an element, we subtract its value from s
. This way, s
always represents the current sum of unmarked elements.
The use of a boolean array mark
to track which elements have been marked allows us to quickly check and update the marking status in O(1) time. The pointer j
in the sorted array ensures we don't re-examine elements we've already processed, making the overall traversal through all queries linear with respect to the array size.
Learn more about Sorting and Heap (Priority Queue) patterns.
Solution Approach
Let's walk through the implementation step by step:
1. Initial Setup:
- Calculate the total sum
s = sum(nums)
which represents the sum of all unmarked elements initially - Create a boolean array
mark = [False] * n
to track which elements are marked - Create an array of tuples
arr = [(value, index)]
for each element innums
, then sort it by value first, and by index if values are equal. This gives us the global ordering we need.
2. Processing Queries:
For each query [index, k]
:
-
Mark the specified index:
if not mark[index]: mark[index] = True s -= nums[index]
If the element at
index
hasn't been marked yet, mark it and subtract its value from the running sum. -
Mark k smallest unmarked elements:
while k and j < n: if not mark[arr[j][1]]: mark[arr[j][1]] = True s -= arr[j][0] k -= 1 j += 1
We use a pointer
j
that persists across queries. Starting from where we left off, we traverse the sorted arrayarr
:- Check if the element at original index
arr[j][1]
is unmarked - If unmarked, mark it and subtract its value
arr[j][0]
from the sum - Decrement
k
(one less element to mark) - Move
j
forward regardless of whether we marked the element or not
The loop continues until either
k
reaches 0 (we've marked enough elements) orj
reachesn
(no more elements to check). - Check if the element at original index
-
Record the result: After processing the marking operations, append the current sum
s
to the answer array.
3. Return the answer array containing the sum of unmarked elements after each query.
Time Complexity: O(n log n + n + m) where n is the length of nums and m is the number of queries. The sorting takes O(n log n), and we traverse the sorted array at most once across all queries.
Space Complexity: O(n) for the sorted array and marking 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 trace through a small example to illustrate the solution approach.
Given: nums = [5, 3, 3, 2]
, queries = [[0, 1], [3, 2]]
Step 1: Initial Setup
- Calculate total sum:
s = 5 + 3 + 3 + 2 = 13
- Create marking array:
mark = [False, False, False, False]
- Create and sort array of (value, index) pairs:
- Original pairs:
[(5, 0), (3, 1), (3, 2), (2, 3)]
- After sorting:
arr = [(2, 3), (3, 1), (3, 2), (5, 0)]
- Original pairs:
- Initialize pointer:
j = 0
Step 2: Process Query 1 - [0, 1]
- Mark element at index 0:
mark[0]
is False, so mark it:mark = [True, False, False, False]
- Subtract from sum:
s = 13 - 5 = 8
- Mark k=1 smallest unmarked elements:
- Start at
j=0
:arr[0] = (2, 3)
, index 3 is unmarked- Mark index 3:
mark = [True, False, False, True]
- Subtract from sum:
s = 8 - 2 = 6
- Decrement k:
k = 0
- Increment j:
j = 1
- Mark index 3:
- k is now 0, so stop
- Start at
- Record result:
answer = [6]
Step 3: Process Query 2 - [3, 2]
- Mark element at index 3:
mark[3]
is True (already marked), so skip
- Mark k=2 smallest unmarked elements:
- Continue from
j=1
:arr[1] = (3, 1)
, index 1 is unmarked- Mark index 1:
mark = [True, True, False, True]
- Subtract from sum:
s = 6 - 3 = 3
- Decrement k:
k = 1
- Increment j:
j = 2
- Mark index 1:
- Continue with
j=2
:arr[2] = (3, 2)
, index 2 is unmarked- Mark index 2:
mark = [True, True, True, True]
- Subtract from sum:
s = 3 - 3 = 0
- Decrement k:
k = 0
- Increment j:
j = 3
- Mark index 2:
- k is now 0, so stop
- Continue from
- Record result:
answer = [6, 0]
Final Answer: [6, 0]
The key insight demonstrated here is how the pointer j
persists across queries, allowing us to efficiently find the next smallest unmarked elements without re-scanning the entire sorted array each time.
Solution Implementation
1class Solution:
2 def unmarkedSumArray(self, nums: List[int], queries: List[List[int]]) -> List[int]:
3 # Get the length of the input array
4 n = len(nums)
5
6 # Calculate the initial sum of all elements
7 total_sum = sum(nums)
8
9 # Track which elements have been marked (initially all False)
10 is_marked = [False] * n
11
12 # Create sorted array of (value, original_index) pairs for greedy marking
13 sorted_elements = sorted((value, idx) for idx, value in enumerate(nums))
14
15 # Pointer to track position in sorted array for smallest unmarked elements
16 sorted_pointer = 0
17
18 # Store results for each query
19 result = []
20
21 # Process each query [index_to_mark, num_smallest_to_mark]
22 for index_to_mark, num_smallest_to_mark in queries:
23 # Mark the element at the specified index if not already marked
24 if not is_marked[index_to_mark]:
25 is_marked[index_to_mark] = True
26 total_sum -= nums[index_to_mark]
27
28 # Mark the k smallest unmarked elements
29 while num_smallest_to_mark > 0 and sorted_pointer < n:
30 current_value, current_index = sorted_elements[sorted_pointer]
31
32 # If element is not marked, mark it and update sum
33 if not is_marked[current_index]:
34 is_marked[current_index] = True
35 total_sum -= current_value
36 num_smallest_to_mark -= 1
37
38 # Move to next element in sorted order
39 sorted_pointer += 1
40
41 # Add the sum of unmarked elements after this query
42 result.append(total_sum)
43
44 return result
45
1class Solution {
2 public long[] unmarkedSumArray(int[] nums, int[][] queries) {
3 int n = nums.length;
4
5 // Calculate initial sum of all elements
6 long totalSum = Arrays.stream(nums).asLongStream().sum();
7
8 // Track which indices have been marked
9 boolean[] isMarked = new boolean[n];
10
11 // Create array of [value, originalIndex] pairs for sorting
12 int[][] valueIndexPairs = new int[n][2];
13 for (int i = 0; i < n; i++) {
14 valueIndexPairs[i] = new int[] {nums[i], i};
15 }
16
17 // Sort by value (ascending), then by index if values are equal
18 Arrays.sort(valueIndexPairs, (a, b) -> {
19 if (a[0] == b[0]) {
20 return a[1] - b[1];
21 }
22 return a[0] - b[0];
23 });
24
25 int numQueries = queries.length;
26 long[] result = new long[numQueries];
27
28 // Track position in sorted array for marking smallest unmarked elements
29 int sortedArrayIndex = 0;
30
31 // Process each query
32 for (int queryIndex = 0; queryIndex < numQueries; queryIndex++) {
33 int targetIndex = queries[queryIndex][0];
34 int elementsToMark = queries[queryIndex][1];
35
36 // Mark the element at targetIndex if not already marked
37 if (!isMarked[targetIndex]) {
38 isMarked[targetIndex] = true;
39 totalSum -= nums[targetIndex];
40 }
41
42 // Mark the smallest 'elementsToMark' unmarked elements
43 while (elementsToMark > 0 && sortedArrayIndex < n) {
44 int originalIndex = valueIndexPairs[sortedArrayIndex][1];
45
46 // Only mark if element is not already marked
47 if (!isMarked[originalIndex]) {
48 isMarked[originalIndex] = true;
49 totalSum -= valueIndexPairs[sortedArrayIndex][0];
50 elementsToMark--;
51 }
52
53 sortedArrayIndex++;
54 }
55
56 // Store the sum of unmarked elements after this query
57 result[queryIndex] = totalSum;
58 }
59
60 return result;
61 }
62}
63
1class Solution {
2public:
3 vector<long long> unmarkedSumArray(vector<int>& nums, vector<vector<int>>& queries) {
4 int n = nums.size();
5
6 // Calculate initial sum of all elements
7 long long totalSum = accumulate(nums.begin(), nums.end(), 0LL);
8
9 // Track which indices have been marked
10 vector<bool> isMarked(n, false);
11
12 // Create pairs of (value, index) for sorting by value
13 vector<pair<int, int>> sortedElements;
14 for (int i = 0; i < n; ++i) {
15 sortedElements.emplace_back(nums[i], i);
16 }
17
18 // Sort elements by value (ascending order)
19 sort(sortedElements.begin(), sortedElements.end());
20
21 // Store results for each query
22 vector<long long> result;
23 int numQueries = queries.size();
24
25 // Track position in sorted array for marking smallest unmarked elements
26 int sortedIndex = 0;
27
28 // Process each query
29 for (int queryIdx = 0; queryIdx < numQueries; ++queryIdx) {
30 int targetIndex = queries[queryIdx][0];
31 int elementsToMark = queries[queryIdx][1];
32
33 // Mark the element at the specified index if not already marked
34 if (!isMarked[targetIndex]) {
35 isMarked[targetIndex] = true;
36 totalSum -= nums[targetIndex];
37 }
38
39 // Mark the k smallest unmarked elements
40 while (elementsToMark > 0 && sortedIndex < n) {
41 int currentIndex = sortedElements[sortedIndex].second;
42 int currentValue = sortedElements[sortedIndex].first;
43
44 if (!isMarked[currentIndex]) {
45 isMarked[currentIndex] = true;
46 totalSum -= currentValue;
47 elementsToMark--;
48 }
49 sortedIndex++;
50 }
51
52 // Add current sum of unmarked elements to result
53 result.push_back(totalSum);
54 }
55
56 return result;
57 }
58};
59
1/**
2 * Processes queries to mark array elements and returns sum of unmarked elements after each query
3 * @param nums - The input array of numbers
4 * @param queries - Array of [index, k] pairs where index is position to mark and k is count of smallest unmarked to mark
5 * @returns Array containing sum of unmarked elements after each query
6 */
7function unmarkedSumArray(nums: number[], queries: number[][]): number[] {
8 const arrayLength: number = nums.length;
9
10 // Calculate initial sum of all elements
11 let remainingSum: number = nums.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
12
13 // Track which indices have been marked
14 const isMarked: boolean[] = Array(arrayLength).fill(false);
15
16 // Create array of [value, originalIndex] pairs for sorting
17 const valueIndexPairs: [number, number][] = nums.map((value, index) => [value, index]);
18
19 // Sort by value (ascending), then by index if values are equal
20 valueIndexPairs.sort((a, b) => {
21 if (a[0] === b[0]) {
22 return a[1] - b[1];
23 }
24 return a[0] - b[0];
25 });
26
27 // Pointer for traversing sorted array
28 let sortedArrayPointer: number = 0;
29
30 // Store results after each query
31 const results: number[] = [];
32
33 // Process each query
34 for (const [targetIndex, countToMark] of queries) {
35 // Mark the element at the specified index if not already marked
36 if (!isMarked[targetIndex]) {
37 isMarked[targetIndex] = true;
38 remainingSum -= nums[targetIndex];
39 }
40
41 // Mark k smallest unmarked elements
42 let remainingToMark: number = countToMark;
43 while (remainingToMark > 0 && sortedArrayPointer < arrayLength) {
44 const currentOriginalIndex: number = valueIndexPairs[sortedArrayPointer][1];
45 const currentValue: number = valueIndexPairs[sortedArrayPointer][0];
46
47 // If current element is not marked, mark it
48 if (!isMarked[currentOriginalIndex]) {
49 isMarked[currentOriginalIndex] = true;
50 remainingSum -= currentValue;
51 remainingToMark--;
52 }
53
54 sortedArrayPointer++;
55 }
56
57 // Add the remaining sum after this query to results
58 results.push(remainingSum);
59 }
60
61 return results;
62}
63
Time and Space Complexity
Time Complexity: O(n × log n + m)
, where n
is the length of the array nums
and m
is the length of queries
.
- Creating the sorted array
arr
withsorted((x, i) for i, x in enumerate(nums))
takesO(n × log n)
time. - The main loop iterates through each query in
queries
, which takesO(m)
iterations. - Within each query iteration:
- Marking
index
and updating sums
takesO(1)
time. - The inner while loop processes at most
k
elements, but crucially, the pointerj
only moves forward throughout the entire execution, never resets.
- Marking
- Since
j
can increment at mostn
times across all queries combined (as it traverses the sorted array once), the total work done by the inner while loop across all queries isO(n)
. - Therefore, the overall time complexity is
O(n × log n + m + n) = O(n × log n + m)
.
Since m
(number of queries) is typically bounded by n
in most practical cases, and the sorting dominates, this simplifies to O(n × log n)
.
Space Complexity: O(n)
- The
mark
array usesO(n)
space to track marked indices. - The
arr
array stores tuples of(value, index)
for alln
elements, usingO(n)
space. - The
ans
array stores results for each query, usingO(m)
space, but this is typically considered output space. - Other variables (
s
,j
,index
,k
) useO(1)
space. - Total auxiliary space complexity is
O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Resetting the Sorted Array Pointer for Each Query
The Mistake:
A common error is resetting the pointer sorted_pointer
to 0 for each query, thinking we need to find the k smallest elements fresh each time:
for index_to_mark, num_smallest_to_mark in queries: sorted_pointer = 0 # WRONG: Resetting pointer each time while num_smallest_to_mark > 0 and sorted_pointer < n: # ... marking logic
Why It's Wrong: This would cause us to repeatedly check already-marked elements from the beginning of the sorted array, leading to:
- Incorrect results (marking wrong elements)
- Inefficient time complexity O(n*m) instead of O(n+m)
The Fix: Keep the pointer persistent across all queries. Once an element has been checked (whether marked or skipped), we never need to check it again for finding the smallest unmarked elements.
Pitfall 2: Double-Subtracting When Element Already Marked
The Mistake: Not checking if an element is already marked before subtracting from the sum:
# WRONG: Always subtract, even if already marked total_sum -= nums[index_to_mark] is_marked[index_to_mark] = True
Why It's Wrong:
If the element at index_to_mark
was already marked in a previous query (either as a specified index or as one of the k smallest), subtracting its value again would give an incorrect sum.
The Fix: Always check the marking status before updating:
if not is_marked[index_to_mark]: is_marked[index_to_mark] = True total_sum -= nums[index_to_mark]
Pitfall 3: Using the Wrong Value When Subtracting from Sum
The Mistake: When marking the k smallest elements, using the wrong value for subtraction:
# WRONG: Using the index instead of value if not is_marked[current_index]: is_marked[current_index] = True total_sum -= current_index # Should be current_value!
Why It's Wrong:
The sorted array contains tuples of (value, original_index)
. We need to subtract the actual value of the element, not its index position.
The Fix: Properly destructure the tuple and use the correct component:
current_value, current_index = sorted_elements[sorted_pointer] if not is_marked[current_index]: is_marked[current_index] = True total_sum -= current_value # Correct: subtract the value
How many ways can you arrange the three letters A, B and C?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
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
Want a Structured Path to Master System Design Too? Don’t Miss This!