Facebook Pixel

2374. Node With Highest Edge Score

MediumGraphHash Table
Leetcode Link

Problem Description

You have a directed graph with n nodes, where each node is labeled from 0 to n - 1. Each node has exactly one outgoing edge (points to exactly one other node).

The graph is represented by an array edges of length n, where edges[i] tells you that there is a directed edge from node i to node edges[i].

The edge score of a node is calculated by adding up all the labels of nodes that point to it. For example, if nodes 2, 3, and 5 all point to node 7, then the edge score of node 7 would be 2 + 3 + 5 = 10.

Your task is to find the node with the highest edge score. If there's a tie (multiple nodes have the same highest score), return the node with the smallest index.

Example walkthrough:

  • If edges = [1, 0, 0, 0, 2], then:

    • Node 0 points to node 1
    • Node 1 points to node 0
    • Node 2 points to node 0
    • Node 3 points to node 0
    • Node 4 points to node 2
  • Edge scores would be:

    • Node 0: receives edges from nodes 1, 2, 3 β†’ score = 1 + 2 + 3 = 6
    • Node 1: receives edge from node 0 β†’ score = 0
    • Node 2: receives edge from node 4 β†’ score = 4
    • Nodes 3 and 4: no incoming edges β†’ score = 0
  • The answer would be node 0 with the highest score of 6.

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

Intuition

Since we need to calculate the edge score for each node (the sum of labels of all nodes pointing to it), we can think of this as a counting problem.

The key insight is that we don't need to build the entire graph structure or do any complex traversal. We simply need to track how much "score" each node accumulates from incoming edges.

As we iterate through the edges array, for each node i with its outgoing edge to node edges[i], we know that node i contributes its label value (which is i itself) to the edge score of node edges[i]. So we can maintain a counter array where cnt[j] keeps track of the total edge score for node j.

The clever part is that we can update our answer on the fly as we process each edge. Whenever we update a node's edge score, we immediately check if this node should become our new answer. This way, we only need a single pass through the data.

For handling ties (when multiple nodes have the same score), we need to return the smallest index. So when comparing scores, if we find a node with a higher score, we update our answer. If we find a node with the same score as our current answer, we only update if this new node has a smaller index.

This approach is efficient because:

  1. We process each edge exactly once
  2. We don't need to store the graph structure
  3. We find the answer in a single traversal without needing a separate step to find the maximum

Learn more about Graph patterns.

Solution Approach

We implement the solution using a single traversal approach with an auxiliary array to track edge scores.

Data Structure:

  • Create an array cnt of size n where cnt[j] stores the edge score of node j. Initialize all values to 0.
  • Maintain a variable ans to track the node with the highest edge score, initially set to 0.

Algorithm Steps:

  1. Traverse the edges array: Use enumerate to iterate through edges, getting both index i (the source node) and value j (the destination node).

  2. Update edge scores: For each edge from node i to node j:

    • Add i to cnt[j]: cnt[j] += i
    • This accumulates the labels of all nodes pointing to j
  3. Update the answer: After updating cnt[j], check if node j should become the new answer:

    • If cnt[ans] < cnt[j]: Node j has a higher score, so update ans = j
    • If cnt[ans] == cnt[j] and j < ans: Node j has the same score but smaller index, so update ans = j
  4. Return the result: After processing all edges, return ans

Example walkthrough with edges = [1, 0, 0, 0, 2]:

  • Initially: cnt = [0, 0, 0, 0, 0], ans = 0
  • i=0, j=1: cnt[1] += 0 β†’ cnt = [0, 0, 0, 0, 0], ans = 0
  • i=1, j=0: cnt[0] += 1 β†’ cnt = [1, 0, 0, 0, 0], ans = 0
  • i=2, j=0: cnt[0] += 2 β†’ cnt = [3, 0, 0, 0, 0], ans = 0
  • i=3, j=0: cnt[0] += 3 β†’ cnt = [6, 0, 0, 0, 0], ans = 0
  • i=4, j=2: cnt[2] += 4 β†’ cnt = [6, 0, 4, 0, 0], ans = 0
  • Return 0 (highest score is 6)

The time complexity is O(n) as we traverse the edges array once, and space complexity is O(n) for the cnt array.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with edges = [2, 0, 1, 2].

This means:

  • Node 0 β†’ Node 2
  • Node 1 β†’ Node 0
  • Node 2 β†’ Node 1
  • Node 3 β†’ Node 2

Step-by-step execution:

Initialize: cnt = [0, 0, 0, 0], ans = 0

Processing edge from node 0 to node 2:

  • i = 0, j = 2
  • cnt[2] += 0 β†’ cnt = [0, 0, 0, 0]
  • Check if we should update ans:
    • cnt[0] = 0 vs cnt[2] = 0 (equal)
    • Since scores are equal but ans = 0 < 2, keep ans = 0

Processing edge from node 1 to node 0:

  • i = 1, j = 0
  • cnt[0] += 1 β†’ cnt = [1, 0, 0, 0]
  • Check if we should update ans:
    • cnt[0] = 1 > cnt[0] = 1 (current ans is already 0)
    • Keep ans = 0

Processing edge from node 2 to node 1:

  • i = 2, j = 1
  • cnt[1] += 2 β†’ cnt = [1, 2, 0, 0]
  • Check if we should update ans:
    • cnt[0] = 1 < cnt[1] = 2 (node 1 has higher score)
    • Update ans = 1

Processing edge from node 3 to node 2:

  • i = 3, j = 2
  • cnt[2] += 3 β†’ cnt = [1, 2, 3, 0]
  • Check if we should update ans:
    • cnt[1] = 2 < cnt[2] = 3 (node 2 has higher score)
    • Update ans = 2

Final result: Return 2 (node 2 has the highest edge score of 3)

The edge scores are:

  • Node 0: score = 1 (from node 1)
  • Node 1: score = 2 (from node 2)
  • Node 2: score = 3 (from nodes 0 and 3: 0 + 3 = 3)
  • Node 3: score = 0 (no incoming edges)

Solution Implementation

1class Solution:
2    def edgeScore(self, edges: List[int]) -> int:
3        # Initialize the node with the highest edge score (starts with node 0)
4        max_score_node = 0
5      
6        # Create an array to store the edge score for each node
7        # edge_scores[i] represents the sum of all node indices that point to node i
8        edge_scores = [0] * len(edges)
9      
10        # Iterate through each edge in the graph
11        # i is the source node index, target_node is the destination node
12        for source_node, target_node in enumerate(edges):
13            # Add the source node index to the edge score of the target node
14            edge_scores[target_node] += source_node
15          
16            # Update the node with maximum edge score
17            # If current target node has higher score, or same score but smaller index
18            if (edge_scores[max_score_node] < edge_scores[target_node] or 
19                (edge_scores[max_score_node] == edge_scores[target_node] and 
20                 target_node < max_score_node)):
21                max_score_node = target_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
4        int numberOfNodes = edges.length;
5      
6        // Array to store the edge score for each node
7        // Using long to prevent integer overflow when summing indices
8        long[] edgeScores = new long[numberOfNodes];
9      
10        // Variable to track the node with the highest edge score
11        int nodeWithMaxScore = 0;
12      
13        // Iterate through each node to calculate edge scores
14        for (int currentNode = 0; currentNode < numberOfNodes; ++currentNode) {
15            // Get the target node that current node points to
16            int targetNode = edges[currentNode];
17          
18            // Add the current node's index to the target node's edge score
19            edgeScores[targetNode] += currentNode;
20          
21            // Update the node with maximum score if:
22            // 1. Current target node has higher score than the current max, OR
23            // 2. Scores are equal but target node has smaller index (tiebreaker)
24            if (edgeScores[nodeWithMaxScore] < edgeScores[targetNode] || 
25                (edgeScores[nodeWithMaxScore] == edgeScores[targetNode] && targetNode < nodeWithMaxScore)) {
26                nodeWithMaxScore = targetNode;
27            }
28        }
29      
30        // Return the node with the highest edge score
31        return nodeWithMaxScore;
32    }
33}
34
1class Solution {
2public:
3    int edgeScore(vector<int>& edges) {
4        // Get the number of nodes in the graph
5        int n = edges.size();
6      
7        // Create an array to store the edge score for each node
8        // Using long long to prevent integer overflow when summing indices
9        vector<long long> edgeScores(n);
10      
11        // Initialize the result with node 0
12        int maxScoreNode = 0;
13      
14        // Iterate through each node to calculate edge scores
15        for (int currentNode = 0; currentNode < n; ++currentNode) {
16            // Get the target node that currentNode points to
17            int targetNode = edges[currentNode];
18          
19            // Add the current node's index to the target node's edge score
20            edgeScores[targetNode] += currentNode;
21          
22            // Update the node with maximum edge score
23            // If the new score is higher, or if scores are equal but target node has smaller index
24            if (edgeScores[maxScoreNode] < edgeScores[targetNode] || 
25                (edgeScores[maxScoreNode] == edgeScores[targetNode] && targetNode < maxScoreNode)) {
26                maxScoreNode = targetNode;
27            }
28        }
29      
30        // Return the node with the highest edge score
31        return maxScoreNode;
32    }
33};
34
1/**
2 * Calculates the edge score by finding the node with the maximum sum of indices
3 * of nodes pointing to it. In case of a tie, returns the smaller node index.
4 * 
5 * @param edges - Array where edges[i] represents an edge from node i to node edges[i]
6 * @returns The node index with the highest edge score
7 */
8function edgeScore(edges: number[]): number {
9    const nodeCount: number = edges.length;
10  
11    // Array to store the sum of indices for each target node
12    const indexSumByNode: number[] = Array(nodeCount).fill(0);
13  
14    // Track the node with the maximum score
15    let maxScoreNode: number = 0;
16  
17    // Iterate through each edge and calculate scores
18    for (let sourceNode = 0; sourceNode < nodeCount; ++sourceNode) {
19        const targetNode: number = edges[sourceNode];
20      
21        // Add the source node index to the target node's score
22        indexSumByNode[targetNode] += sourceNode;
23      
24        // Update the answer if we found a better node:
25        // 1. Higher score, or
26        // 2. Same score but smaller node index
27        if (indexSumByNode[maxScoreNode] < indexSumByNode[targetNode] || 
28            (indexSumByNode[maxScoreNode] === indexSumByNode[targetNode] && targetNode < maxScoreNode)) {
29            maxScoreNode = targetNode;
30        }
31    }
32  
33    return maxScoreNode;
34}
35

Time and Space Complexity

The time complexity is O(n), where n is the length of the array edges. The algorithm iterates through the edges array exactly once using enumerate(), performing constant-time operations for each element: accessing and updating the cnt array, comparing values, and potentially updating the ans variable.

The space complexity is O(n), where n is the length of the array edges. This is due to the creation of the cnt array with size equal to len(edges), which stores the accumulated scores for each node. The remaining variables (ans, i, j) use constant space.

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

Common Pitfalls

1. Incorrect Tie-Breaking Logic

A common mistake is implementing the tie-breaking condition incorrectly when multiple nodes have the same highest edge score.

Incorrect Implementation:

# Wrong: This updates even when target_node has a larger index
if edge_scores[max_score_node] <= edge_scores[target_node]:
    max_score_node = target_node

Why it's wrong: Using <= without checking the index means that when scores are equal, we always update to the latest node we encounter, not necessarily the one with the smallest index.

Correct Implementation:

if (edge_scores[max_score_node] < edge_scores[target_node] or 
    (edge_scores[max_score_node] == edge_scores[target_node] and 
     target_node < max_score_node)):
    max_score_node = target_node

2. Integer Overflow in Other Languages

While Python handles large integers automatically, in languages like Java or C++, the sum of node indices could overflow for large graphs.

Problem Example: For a graph with n=100,000 nodes where many nodes point to a single node, the edge score could exceed the range of a 32-bit integer.

Solution: Use appropriate data types:

  • Java: Use long[] instead of int[] for edge scores
  • C++: Use long long instead of int
// Java example
long[] edgeScores = new long[edges.length];  // Use long to prevent overflow

3. Forgetting to Initialize the Answer Variable

Some implementations might forget to properly initialize the answer variable or start with an invalid node index.

Incorrect:

max_score_node = -1  # Wrong: -1 is not a valid node
# or forgetting to initialize at all

Correct:

max_score_node = 0  # Start with node 0 as the initial answer

This ensures that even if all nodes have an edge score of 0, we still return a valid node (node 0).

Discover Your Strengths and Weaknesses: Take Our 3-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