2374. Node With Highest Edge Score
Problem Description
In this problem, we're given a directed graph consisting of n
nodes. These nodes are uniquely labeled from 0
to n - 1
. The special thing about this graph is that each node has exactly one outgoing edge, which means that every node points to exactly one other node. This setup creates a series of chains and possibly cycles within the graph.
The graph is described using an array edges
of length n
, where the value edges[i]
represents the node that node i
points to. The problem asks us to compute the "edge score" for each node. The edge score of a node is the sum of the labels of all nodes that point to it.
Our task is to identify the node with the highest edge score. If there happens to be a tie where multiple nodes have the same highest edge score, we should return the node with the smallest index among them.
To summarize, we are to calculate the edge scores, find the maximum, and return the node that obtains it, resolving ties by choosing the lowest-indexed node.
Intuition
To solve this problem, our approach involves two main steps:
-
Calculating Edge Scores: To calculate the edge score of each node efficiently, we can traverse the graph once and increment the edge score for the target nodes. For instance, if there is an edge from node
i
to nodej
, we addi
(the label of the starting node) to the edge score of nodej
(the destination node). We can keep a count of these edge scores using a data structure such as aCounter
from Python's collections module, where the keys correspond to node indices and the values to their edge scores. -
Finding the Node with the Highest Edge Score: After we have all the edge scores, we iterate through them to find the maximum score. While doing this, we must also keep track of the node index because if multiple nodes have the same edge score, we need to return the one with the smallest index. An efficient way to tackle this is to initialize a variable (let's say
ans
) to store the index of the node with the current highest edge score. We iterate through all possible node indices, compare their edge scores with the current highest, and updateans
when we find a higher score or if the score is the same but the node index is smaller.
This approach ensures we traverse the graph only once to compute the scores and a second time to find the maximum score with the smallest node index—both operations having linear time complexity, which is efficient.
Learn more about Graph patterns.
Solution Approach
To implement the solution as described in the intuition, we're using a counter and a for-loop construct. Here's the step-by-step breakdown of the implementation:
-
Initializing the Counter: We start by initializing a Counter, which is a special dictionary that lets us keep a tally for each element. It is a part of Python's built-in
collections
module. In our case, each element corresponds to a node and its tally to the node's edge score.cnt = Counter()
-
Calculating Edge Scores: We loop over each edge in the
edges
list with its index. At each step, we increment the edge score of the node pointed to by the current index. The index indicates the starting node (contributing to the edge score) andedges[i]
the destination node. The score of the destination node is increased by the label of the starting node (which is its indexi
).for i, v in enumerate(edges): cnt[v] += i
-
Finding the Node with the Highest Edge Score: We initialize a variable
ans
to keep track of the index of the node with the highest edge score found so far, starting with the first node (0
).Then, we iterate over the range of node indices, using another for-loop. For each index, we compare its edge score (
cnt[i]
) with the edge score of the current answer (cnt[ans]
). If we find a higher edge score, or if the edge scores are equal and the current index is less thanans
(implying a smaller node index), we updateans
.ans = 0 for i in range(len(edges)): if cnt[ans] < cnt[i]: ans = i
-
Return the Result: Finally, after finishing the iteration, the variable
ans
holds the index of the node with the highest edge score. We returnans
as the final result.return ans
The combination of Counter to tally the score and a for-loop to determine the maximum ensures a straightforward and efficient implementation. It uses O(n) time for computing the edge scores and O(n) time to identify the node with the highest edge score, leading to an overall linear time complexity, where n is the number of nodes. The space complexity is also O(n) due to the storage needed for the Counter. This solution leverages the characteristics of our graph (each node pointing to exactly one other node) to maintain simplicity and efficiency.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let’s walk through a small example to illustrate the solution approach.
Imagine a graph represented by the edges
array: [1, 2, 3, 4, 0]
. This array means:
- Node
0
points to node1
- Node
1
points to node2
- Node
2
points to node3
- Node
3
points to node4
- Node
4
points to node0
Now, following the step-by-step solution approach:
-
Initializing the Counter:
cnt = Counter()
-
Calculating Edge Scores:
As per our edges array:
cnt[1] += 0 # node 0 points to node 1 cnt[2] += 1 # node 1 points to node 2 cnt[3] += 2 # node 2 points to node 3 cnt[4] += 3 # node 3 points to node 4 cnt[0] += 4 # node 4 points to node 0
After this loop:
cnt = {1: 0, 2: 1, 3: 2, 4: 3, 0: 4}
-
Finding the Node with the Highest Edge Score:
Initialize
ans = 0
. Then we compare:cnt[0]
is4
.ans
remains0
.cnt[1]
is0
. No change sincecnt[0]
>cnt[1]
.cnt[2]
is1
. No change sincecnt[0]
>cnt[2]
.cnt[3]
is2
. No change sincecnt[0]
>cnt[3]
.cnt[4]
is3
. No change sincecnt[0]
>cnt[4]
.
After comparing all,
ans
is still0
as it has the highest score4
. -
Return the Result:
return ans # returns 0
With this example, we can see how the Counter was used to calculate edge scores by aggregating contributions from nodes that point to a specific node. Afterward, we iterated through the node indices, keeping track of the node with the current highest score and returned the one with the smallest index in case of a tie. The node with index 0
has the highest edge score of 4
in this example, so it is the answer.
Solution Implementation
1from collections import Counter # Import Counter class from collections
2
3class Solution:
4 def edgeScore(self, edges: List[int]) -> int:
5 # Initialize a Counter to keep track of the scores for each node
6 node_scores = Counter()
7
8 # Iterate over the list of edges with their indices
9 for index, node in enumerate(edges):
10 # Accumulate the index values for each node to compute the edge score
11 node_scores[node] += index
12
13 # Initialize the variable that will hold the node with the highest edge score
14 max_score_node = 0
15
16 # Traverse the nodes to find the one with the highest edge score
17 # In case of a tie, the node with the lower index is selected
18 for node in range(len(edges)):
19 # Update the max_score_node if the current node has a higher score
20 if node_scores[max_score_node] < node_scores[node]:
21 max_score_node = node
22
23 # Return the node with the highest edge score
24 return max_score_node
25
1class Solution {
2 public int edgeScore(int[] edges) {
3 // Get the number of nodes in the graph.
4 int numNodes = edges.length;
5 // Create an array to keep track of the cumulative edge scores for each node.
6 long[] edgeScores = new long[numNodes];
7
8 // Iterate over the array and accumulate edge scores.
9 for (int i = 0; i < numNodes; ++i) {
10 // Increment the edge score of the destination node by the index of the current node.
11 edgeScores[edges[i]] += i;
12 }
13
14 // Initialize 'answer' to the first node's index (0) by default.
15 int answer = 0;
16 // Iterate over edgeScores to find the node with the highest edge score.
17 for (int i = 0; i < numNodes; ++i) {
18 // If the current node's edge score is higher than the score of the answer node,
19 // then update the answer to the current node's index.
20 if (edgeScores[answer] < edgeScores[i]) {
21 answer = i;
22 }
23 }
24
25 // Return the node with the highest edge score.
26 return answer;
27 }
28}
29
1class Solution {
2public:
3 // Function that calculates the edge score for each node and returns the node with the highest score.
4 int edgeScore(vector<int>& edges) {
5 int numNodes = edges.size(); // The number of nodes is determined by the size of the edges array.
6 vector<long long> nodeScores(numNodes, 0); // Initialize a vector to store the score for each node.
7
8 // Calculate the score for each node by adding the index of the node it points to its score.
9 for (int idx = 0; idx < numNodes; ++idx) {
10 nodeScores[edges[idx]] += idx;
11 }
12
13 // The node with the highest score. Initialized to the first node (index 0).
14 int nodeWithMaxScore = 0;
15
16 // Iterate through the node scores to find the node with the highest score.
17 // In case of a tie, the node with the lower index wins, which is naturally handled
18 // due to the non-decreasing traversal of the array.
19 for (int i = 0; i < numNodes; ++i) {
20 if (nodeScores[nodeWithMaxScore] < nodeScores[i]) {
21 nodeWithMaxScore = i; // Update the node with the maximum score.
22 }
23 }
24
25 // Return the index of the node with the maximum score.
26 return nodeWithMaxScore;
27 }
28};
29
1function edgeScore(edges: number[]): number {
2 // Length of the 'edges' array
3 const numberOfNodes: number = edges.length;
4
5 // Initialize an array to store the sum of the indices for each edge
6 const edgeScores: number[] = new Array(numberOfNodes).fill(0);
7
8 // Iterate over the 'edges' array to calculate the sum of indices for each node
9 for (let index = 0; index < numberOfNodes; index++) {
10 // Update the score for the node pointed to by the current edge
11 edgeScores[edges[index]] += index;
12 }
13
14 // Variable to hold the index of the node with the highest score
15 let highestScoreNode: number = 0;
16
17 // Find the node with the maximum edge score
18 for (let node = 0; node < numberOfNodes; node++) {
19 // If the current node has a higher score than the highest recorded, update the highestScoreNode
20 if (edgeScores[highestScoreNode] < edgeScores[node]) {
21 highestScoreNode = node;
22 }
23 }
24
25 // Return the node with the highest edge score
26 return highestScoreNode;
27}
28
Time and Space Complexity
Time complexity
The given code consists primarily of two parts: a loop to accumulate the edge scores and another loop to find the node with the highest edge score. Let's analyze each part to determine the overall time complexity:
-
The
for i, v in enumerate(edges):
iterates through theedges
list once. The length of this list isn
, wheren
is the number of nodes in the graph. Inside this loop, each iteration performs anO(1)
operation, where it updates theCounter
object. Therefore, this loop has a time complexity ofO(n)
. -
The second loop
for i in range(len(edges)):
is also iteratingn
times for each node in the graph. For each iteration, it performs a comparison operation which isO(1)
. Hence, the time complexity of this loop is alsoO(n)
.
Combining both parts, the overall time complexity for the entire function is O(n) + O(n)
which simplifies to O(n)
.
Space complexity
The space complexity is determined by the data structures used in the algorithm:
-
The
cnt
variable is aCounter
object which, in the worst case, will contain an entry for each unique node inedges
. This means its size grows linearly with the number of nodes, contributing a space complexity ofO(n)
. -
The
ans
variable is an integer, which occupiesO(1)
space.
As such, the total space complexity of the algorithm is O(n)
for the Counter
object plus O(1)
for the integer, which results in an overall space complexity of O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
https algomonster s3 us east 2 amazonaws com cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could
LeetCode 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 we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!