Facebook Pixel

2593. Find Score of an Array After Marking All Elements

Problem Description

You have an array nums containing positive integers. Your task is to calculate a score by repeatedly selecting elements from the array following specific rules.

Starting with a score of 0, you need to:

  1. Select the smallest unmarked integer from the array. If multiple elements have the same smallest value, choose the one that appears first (has the smallest index).

  2. Add the selected integer's value to your running score.

  3. Mark the selected element as used, and also mark its two adjacent neighbors (the element immediately to its left and the element immediately to its right, if they exist).

  4. Repeat this process until all elements in the array are marked.

The goal is to return the final score after all elements have been marked.

For example, if you have an array [2, 1, 3, 4, 5, 2]:

  • First iteration: Select 1 at index 1 (smallest unmarked), add 1 to score, mark indices 0, 1, and 2
  • Second iteration: Select 2 at index 5 (smallest unmarked), add 2 to score, mark indices 4 and 5
  • Third iteration: Select 4 at index 3 (only unmarked element left), add 4 to score, mark index 3
  • Final score: 1 + 2 + 4 = 7

The algorithm ensures that when you pick an element, you can't pick its immediate neighbors in future iterations, creating a strategic selection pattern.

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 always select the smallest unmarked element, which naturally suggests using a data structure that can efficiently give us the minimum element - a min heap (priority queue).

Since we're always forced to pick the smallest available element, we don't have much choice in our selection strategy. The challenge is efficiently tracking which elements are marked and finding the next smallest unmarked element.

Here's the thought process:

  1. Why a heap? We need to repeatedly find the minimum element among unmarked elements. A sorted array would work, but once we mark elements, we'd need to skip over them, which could be inefficient. A min heap allows us to always have quick access to the smallest element.

  2. Handling the marking constraint: When we select an element at index i, we must mark indices i-1, i, and i+1. We can use a boolean array vis to track which indices are marked. This is more efficient than removing elements from our data structure.

  3. Dealing with marked elements in the heap: Since we can't remove arbitrary elements from a heap efficiently, we leave marked elements in the heap but skip them when they reach the top. When we pop an element from the heap, we check if it's already marked. If it is, we keep popping until we find an unmarked element.

  4. Why store tuples (value, index)? We need both the value (to maintain heap order and add to our score) and the index (to know which positions to mark in our vis array).

The algorithm essentially becomes: maintain all elements in a min heap, use a separate array to track what's marked, and when extracting from the heap, skip any already-marked elements. This gives us an efficient way to always select the smallest unmarked element while respecting the marking constraints.

Learn more about Sorting and Heap (Priority Queue) patterns.

Solution Approach

We implement the solution using a min heap (priority queue) to efficiently track and retrieve the smallest unmarked element.

Step 1: Initialize Data Structures

  • Create a boolean array vis of size n to track which elements are marked (initially all False)
  • Build a list q containing tuples (value, index) for each element in nums
  • Convert q into a min heap using heapify(q) - this orders elements by value first, then by index if values are equal
  • Initialize ans = 0 to accumulate our score

Step 2: Process Elements from the Heap

While the heap is not empty:

  1. Extract the minimum element: Use heappop(q) to get the tuple (x, i) where x is the value and i is the index

  2. Update the score: Add x to our answer: ans += x

  3. Mark the selected element and its neighbors:

    • Mark the current index: vis[i] = True
    • Mark adjacent indices if they exist:
      for j in (i - 1, i + 1):
          if 0 <= j < n:
              vis[j] = True
  4. Clean up marked elements from heap top:

    • Since we can't efficiently remove arbitrary elements from a heap, we leave marked elements inside
    • But when they bubble up to the top, we skip them:
      while q and vis[q[0][1]]:
          heappop(q)
    • This ensures the next iteration will work with an unmarked element at the heap's top

Step 3: Return the Result

After processing all elements, return ans which contains the accumulated score.

Time Complexity: O(n log n) where n is the length of the array. The heapify operation takes O(n), and we perform up to n heap pop operations, each taking O(log n).

Space Complexity: O(n) for the heap and the visited array.

The elegance of this approach lies in how it handles the marking constraint - rather than trying to remove marked elements from the heap (which would be inefficient), we simply skip over them when they appear at the top, maintaining the heap's efficiency while respecting the problem's rules.

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 walk through the solution with the array nums = [3, 5, 1, 4].

Initial Setup:

  • Create visited array: vis = [False, False, False, False]
  • Build heap with (value, index) pairs: q = [(3, 0), (5, 1), (1, 2), (4, 3)]
  • After heapify: q = [(1, 2), (4, 3), (3, 0), (5, 1)] (min heap structure)
  • Score: ans = 0

Iteration 1:

  • Pop from heap: (1, 2) - smallest value is 1 at index 2
  • Add to score: ans = 0 + 1 = 1
  • Mark index 2 and its neighbors:
    • vis[2] = True (mark selected element)
    • vis[1] = True (mark left neighbor)
    • vis[3] = True (mark right neighbor)
  • Visited array now: [False, True, True, True]
  • Clean heap top: Check if next element in heap is marked
    • Heap top is (3, 0) at index 0, which is not marked, so no cleanup needed

Iteration 2:

  • Pop from heap: (3, 0) - smallest unmarked value is 3 at index 0
  • Add to score: ans = 1 + 3 = 4
  • Mark index 0 and its neighbors:
    • vis[0] = True (mark selected element)
    • No left neighbor (index -1 doesn't exist)
    • vis[1] = True (already marked, no change)
  • Visited array now: [True, True, True, True] - all elements marked
  • Clean heap top:
    • Next would be (4, 3) but index 3 is marked, so pop it
    • Next would be (5, 1) but index 1 is marked, so pop it
    • Heap is now empty

Final Result: Score = 4

The algorithm selected elements in order: 1 (index 2), then 3 (index 0), giving us a total score of 4. Note how selecting the element at index 2 first prevented us from selecting indices 1 and 3 due to the marking rule.

Solution Implementation

1class Solution:
2    def findScore(self, nums: List[int]) -> int:
3        """
4        Calculate score by repeatedly selecting the smallest unvisited element,
5        adding it to the score, and marking it and its adjacent elements as visited.
6      
7        Args:
8            nums: List of integers
9          
10        Returns:
11            Total score calculated
12        """
13        n = len(nums)
14      
15        # Track visited status for each index
16        visited = [False] * n
17      
18        # Create min heap with (value, index) pairs
19        min_heap = [(value, index) for index, value in enumerate(nums)]
20        heapify(min_heap)
21      
22        total_score = 0
23      
24        while min_heap:
25            # Extract minimum element that hasn't been visited
26            value, index = heappop(min_heap)
27          
28            # Add value to total score
29            total_score += value
30          
31            # Mark current index as visited
32            visited[index] = True
33          
34            # Mark adjacent indices as visited
35            for adjacent_index in (index - 1, index + 1):
36                if 0 <= adjacent_index < n:
37                    visited[adjacent_index] = True
38          
39            # Remove all visited elements from top of heap
40            while min_heap and visited[min_heap[0][1]]:
41                heappop(min_heap)
42      
43        return total_score
44
1class Solution {
2    public long findScore(int[] nums) {
3        int n = nums.length;
4      
5        // Track which indices have been marked/visited
6        boolean[] marked = new boolean[n];
7      
8        // Priority queue to store [value, index] pairs
9        // Sort by value first (ascending), then by index if values are equal
10        PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> {
11            if (a[0] == b[0]) {
12                return a[1] - b[1];  // Compare indices if values are equal
13            }
14            return a[0] - b[0];  // Compare values
15        });
16      
17        // Add all elements with their indices to the priority queue
18        for (int i = 0; i < n; i++) {
19            minHeap.offer(new int[] {nums[i], i});
20        }
21      
22        long totalScore = 0;
23      
24        // Process elements from smallest to largest
25        while (!minHeap.isEmpty()) {
26            // Get the smallest unmarked element
27            int[] current = minHeap.poll();
28            int value = current[0];
29            int index = current[1];
30          
31            // Add value to score and mark current index
32            totalScore += value;
33            marked[index] = true;
34          
35            // Mark adjacent indices (left and right neighbors)
36            if (index - 1 >= 0) {
37                marked[index - 1] = true;
38            }
39            if (index + 1 < n) {
40                marked[index + 1] = true;
41            }
42          
43            // Remove already marked elements from the top of the heap
44            while (!minHeap.isEmpty() && marked[minHeap.peek()[1]]) {
45                minHeap.poll();
46            }
47        }
48      
49        return totalScore;
50    }
51}
52
1class Solution {
2public:
3    long long findScore(vector<int>& nums) {
4        int n = nums.size();
5      
6        // Track which indices have been marked/visited
7        vector<bool> isMarked(n, false);
8      
9        // Min heap to store (value, index) pairs, sorted by value first, then index
10        using ValueIndexPair = pair<int, int>;
11        priority_queue<ValueIndexPair, vector<ValueIndexPair>, greater<ValueIndexPair>> minHeap;
12      
13        // Add all elements with their indices to the min heap
14        for (int i = 0; i < n; ++i) {
15            minHeap.emplace(nums[i], i);
16        }
17      
18        long long totalScore = 0;
19      
20        // Process elements from smallest to largest
21        while (!minHeap.empty()) {
22            // Get the smallest unmarked element
23            auto [value, index] = minHeap.top();
24            minHeap.pop();
25          
26            // Skip if this index is already marked
27            if (isMarked[index]) {
28                continue;
29            }
30          
31            // Add the value to the total score
32            totalScore += value;
33          
34            // Mark the current index
35            isMarked[index] = true;
36          
37            // Mark the right neighbor if it exists
38            if (index + 1 < n) {
39                isMarked[index + 1] = true;
40            }
41          
42            // Mark the left neighbor if it exists
43            if (index - 1 >= 0) {
44                isMarked[index - 1] = true;
45            }
46        }
47      
48        return totalScore;
49    }
50};
51
1// Interface to store value-index pairs for priority queue
2interface ValueIndexPair {
3    value: number;
4    index: number;
5}
6
7/**
8 * Calculates the score by selecting elements from the array
9 * When an element is selected, its adjacent elements are marked as visited
10 * Elements are processed in ascending order of value (and index as tiebreaker)
11 * @param nums - The input array of numbers
12 * @returns The total score calculated
13 */
14function findScore(nums: number[]): number {
15    // Initialize priority queue with custom comparator
16    // Sort by value first, then by index if values are equal
17    const priorityQueue = new PriorityQueue({
18        compare: (a: ValueIndexPair, b: ValueIndexPair) => 
19            (a.value === b.value ? a.index - b.index : a.value - b.value),
20    });
21  
22    const arrayLength = nums.length;
23    // Track visited elements
24    const isVisited: boolean[] = new Array(arrayLength).fill(false);
25  
26    // Add all elements to priority queue with their indices
27    for (let i = 0; i < arrayLength; ++i) {
28        priorityQueue.enqueue({ value: nums[i], index: i });
29    }
30  
31    let totalScore = 0;
32  
33    // Process elements from priority queue
34    while (!priorityQueue.isEmpty()) {
35        const { value: currentValue, index: currentIndex } = priorityQueue.dequeue()!;
36      
37        // Skip if current element is already visited
38        if (isVisited[currentIndex]) {
39            continue;
40        }
41      
42        // Add current value to score
43        totalScore += currentValue;
44      
45        // Mark current element as visited
46        isVisited[currentIndex] = true;
47      
48        // Mark left adjacent element as visited if exists
49        if (currentIndex - 1 >= 0) {
50            isVisited[currentIndex - 1] = true;
51        }
52      
53        // Mark right adjacent element as visited if exists
54        if (currentIndex + 1 < arrayLength) {
55            isVisited[currentIndex + 1] = true;
56        }
57      
58        // Remove consecutive visited elements from front of queue
59        while (!priorityQueue.isEmpty() && isVisited[priorityQueue.front()!.index]) {
60            priorityQueue.dequeue();
61        }
62    }
63  
64    return totalScore;
65}
66

Time and Space Complexity

Time Complexity: O(n × log n)

The time complexity is dominated by the following operations:

  • Creating the list q with tuples (x, i) takes O(n) time
  • The heapify(q) operation takes O(n) time to build the heap
  • The while loop performs at most n iterations (since each element is processed once)
  • Each iteration involves:
    • A heappop(q) operation which takes O(log n) time
    • Marking neighbors as visited takes O(1) time
    • The inner while loop removes already visited elements from the heap. In the worst case, we might perform up to n total heappop operations across all iterations
  • The total number of heappop operations is at most 2n (each element is popped at most once in the main loop, and potentially once more if it was marked as visited)
  • Therefore, the total time for all heap operations is O(n × log n)

Space Complexity: O(n)

The space complexity consists of:

  • The vis array of size n takes O(n) space
  • The heap q contains n tuples, each tuple storing a value and an index, taking O(n) space
  • The total space complexity is O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Inefficient Heap Cleanup Strategy

The Problem: The current solution removes visited elements from the heap only when they appear at the top. This means visited elements remain in the heap throughout execution, potentially causing the heap to contain many "dead" elements. In worst-case scenarios, this leads to repeatedly popping already-visited elements, degrading performance.

Example Scenario: Consider an array like [1, 2, 3, 4, 5]:

  • When we select 1 at index 0, we mark indices 0 and 1 as visited
  • The element 2 at index 1 remains in the heap even though it's marked
  • Later iterations waste time popping and discarding these marked elements

Better Solution: Instead of using a heap, use a sorted list of (value, index) pairs and iterate through it once:

def findScore(self, nums: List[int]) -> int:
    n = len(nums)
    visited = [False] * n
  
    # Create sorted list of (value, index) pairs
    sorted_pairs = sorted(enumerate(nums), key=lambda x: (x[1], x[0]))
  
    total_score = 0
    for index, value in sorted_pairs:
        if not visited[index]:
            total_score += value
            visited[index] = True
            # Mark neighbors
            if index > 0:
                visited[index - 1] = True
            if index < n - 1:
                visited[index + 1] = True
  
    return total_score

Pitfall 2: Misunderstanding the Marking Order

The Problem: A common mistake is thinking we need to mark elements in the order they appear in the heap, rather than checking if an element is already marked before processing it. Some implementations try to prevent adding neighbors to the heap or attempt complex heap modifications.

Incorrect Approach:

# Wrong: Trying to prevent neighbors from being added to heap
for i in range(n):
    if not is_neighbor_of_selected[i]:
        heappush(heap, (nums[i], i))

Correct Understanding: The solution correctly handles this by:

  1. Adding all elements to the heap initially
  2. Checking visited status when elements are popped
  3. Skipping already-visited elements

This approach is simpler and avoids the complexity of dynamically managing heap contents based on neighbor relationships.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More