Facebook Pixel

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:

  1. Mark the element at position index_i (if it's not already marked)
  2. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 in nums, 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 array arr:

    • 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) or j reaches n (no more elements to check).

  • 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 Evaluator

Example 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)]
  • 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
    • k is now 0, so stop
  • 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
    • 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
    • k is now 0, so stop
  • 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 with sorted((x, i) for i, x in enumerate(nums)) takes O(n × log n) time.
  • The main loop iterates through each query in queries, which takes O(m) iterations.
  • Within each query iteration:
    • Marking index and updating sum s takes O(1) time.
    • The inner while loop processes at most k elements, but crucially, the pointer j only moves forward throughout the entire execution, never resets.
  • Since j can increment at most n times across all queries combined (as it traverses the sorted array once), the total work done by the inner while loop across all queries is O(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 uses O(n) space to track marked indices.
  • The arr array stores tuples of (value, index) for all n elements, using O(n) space.
  • The ans array stores results for each query, using O(m) space, but this is typically considered output space.
  • Other variables (s, j, index, k) use O(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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More