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:
-
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).
-
Add the selected integer's value to your running score.
-
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).
-
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.
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:
-
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.
-
Handling the marking constraint: When we select an element at index
i
, we must mark indicesi-1
,i
, andi+1
. We can use a boolean arrayvis
to track which indices are marked. This is more efficient than removing elements from our data structure. -
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.
-
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 ourvis
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 sizen
to track which elements are marked (initially allFalse
) - Build a list
q
containing tuples(value, index)
for each element innums
- Convert
q
into a min heap usingheapify(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:
-
Extract the minimum element: Use
heappop(q)
to get the tuple(x, i)
wherex
is the value andi
is the index -
Update the score: Add
x
to our answer:ans += x
-
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
- Mark the current index:
-
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 EvaluatorExample 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
- Heap top is
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
- Next would be
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)
takesO(n)
time - The
heapify(q)
operation takesO(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 takesO(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
totalheappop
operations across all iterations
- A
- The total number of
heappop
operations is at most2n
(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 sizen
takesO(n)
space - The heap
q
containsn
tuples, each tuple storing a value and an index, takingO(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:
- Adding all elements to the heap initially
- Checking visited status when elements are popped
- Skipping already-visited elements
This approach is simpler and avoids the complexity of dynamically managing heap contents based on neighbor relationships.
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
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!