2242. Maximum Score of a Node Sequence
Problem Description
In this LeetCode problem, we are given an undirected graph with n
nodes, where each node has an associated score. The task is to find the maximum possible score of any valid node sequence with exactly 4 nodes. A valid node sequence must meet two criteria: first, each pair of consecutive nodes in the sequence must be connected by an edge, and second, no node can be repeated within the sequence. The score of the sequence is simply the sum of its nodes' scores. If there is no such sequence, we need to return -1
.
An undirected graph means that if there is an edge connecting nodes u
and v
, we can travel from u
to v
and from v
to u
similarly. The node ids range from 0
to n - 1
. The array scores
represents the score of each node, while the array edges
contains pairs of nodes that are connected by an edge. The challenge is to navigate the graph and find the optimal sequence of four nodes that maximizes the score.
Intuition
The intuition behind the solution is to consider node sequences as potential paths of length four. Since we want to maximize the score of such a sequence, we need to think strategically about which nodes we select. Considering every possible path of length four in the graph would be computationally expensive, especially if the graph is large. To make the problem tractable, the solution takes advantage of the following observations:
-
For any two connected nodes
a
andb
, they might be the middle part of the sequence (second and third nodes), and we want to find the best possible candidates to place as the first and fourth nodes. These are the nodes connected toa
andb
, respectively, with the highest scores. -
We only need to consider the three highest-scoring neighbors for each node when trying to form a sequence that includes that node because adding a lower-scoring neighbor would never produce the maximum sum.
With these insights, the solution first constructs a graph representation where each node is mapped to its three highest-scoring neighbors. Then, it iterates over each edge in the original graph, trying to construct sequences by checking all combinations of a
's top scoring neighbors with b
's top scoring neighbors that don't already include a
or b
. This approach vastly reduces the number of potential combinations to consider.
The maximum score found during this process is then returned. If no valid sequence of length four is found, we return -1
.
Solution Approach
The implementation of the solution follows the intuition previously discussed and employs different data structures and algorithms to achieve the optimal path search. Here's a step-wise breakdown of the solution approach:
-
Graph Representation:
-
The algorithm starts by creating a default dictionary
g
that will hold the list of neighboring nodes for each node. In Python,defaultdict(list)
from thecollections
module ensures that each new key starts with an empty list as its value. -
It then iterates through the provided
edges
list to build the undirected graph by adding each node of an edge to the neighbor list of the other. The graph representation is adjacency lists, essentially a mapping from each node to its neighbors.
-
-
Filtering Top Neighbors:
- To reduce the complexity for later steps, the algorithm then filters the neighbors of each node. For each node key in
g
, we keep only the top three neighbors with the highest scores. This is done using thenlargest
function from theheapq
module, which selects the largestn
elements according to the provided key function. In this case,lambda x: scores[x]
is the key function, which ranks neighbors based on their respective scores in thescores
array.
- To reduce the complexity for later steps, the algorithm then filters the neighbors of each node. For each node key in
-
Finding the Optimal Sequence:
-
Now, the algorithm goes through each edge in the original
edges
list, treating the pair of nodes on the edge(a, b)
as the potential middle of the sequence. -
For every such pair, we look at all combinations of the neighbors of
a
(denoted asc
) and the neighbors ofb
(denoted asd
). Here, we apply a crucial constraint: we only considerc
andd
that are distinct from botha
andb
and each other (b != c != d != a
). This ensures we are only creating valid four-node sequences without repeats. -
For every valid combination, we calculate the total score
t
of the sequence by summing the scores ofa
,b
,c
, andd
. We keep track of the maximum score found so far in the variableans
. -
If no sequence is found,
ans
retains its initial value of-1
, otherwise,ans
contains the maximum score of all valid sequences.
-
The key algorithms and data structures used in this solution are:
- Graph representation using adjacency lists: to efficiently store and explore neighbor nodes.
- Heap-based selection: using
nlargest
to efficiently sort and filter the neighbors based on their scores. - Greedy approach: by trying to extend each edge with the highest scoring neighbors, we apply a greedy strategy aimed at maximizing the node sequence score.
- Exhaustive search: within the constraints of the greedy filtering, we still need to exhaustively search through the limited number of potential neighbors for the optimal sequence.
This combination of strategies is designed to find the maximum scoring node sequence of length four with a balance between performance and ensuring that no potential solution is missed.
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 the solution approach with a simple example.
Assume the input graph is represented by:
n = 5
(so we have nodes 0 to 4)scores = [20, 10, 30, 40, 25]
edges = [(0, 1), (1, 2), (2, 3), (3, 4)]
This graph looks like a straight line: 0 -- 1 -- 2 -- 3 -- 4
Step 1: Graph Representation
Create a default dictionary g
to hold the adjacency list of the graph. It will end up looking like this:
g[0] = [1]
g[1] = [0, 2]
g[2] = [1, 3]
g[3] = [2, 4]
g[4] = [3]
Step 2: Filtering Top Neighbors
For each node, we will determine the top 3 scoring neighbors (although in our simple graph, no node has more than two neighbors):
g[0] = [1]
(Only has one neighbor)g[1] = [2]
(Highest-scoring neighbor of node 1 is node 2)g[2] = [3]
(Highest-scoring neighbor of node 2 is node 3)g[3] = [4]
(Highest-scoring neighbor of node 3 is node 4)g[4] = [3]
(Only has one neighbor)
Step 3: Finding the Optimal Sequence
Now, we'll iterate through the edges
to find sequences of 4 nodes:
- For edge
(0, 1)
, our potential sequence is Node 1's high scoring neighbor (Node 2) and one of Node 0's neighbors (Only Node 1). But for a valid sequence, we need 4 unique nodes, and we can't create one because the graph is a straight line and node 1 is already in use. - For edge
(1, 2)
, we can choose Node 1's highest-scoring neighbor, which is Node 0 and Node 2's highest-scoring neighbor, which is Node 3. This results in a potential sequence of[0, 1, 2, 3]
. - For edge
(2, 3)
, we can take Node 2's highest-scoring neighbor, Node 1, and Node 3's highest-scoring neighbor, Node 4. This leads to a potential sequence of[1, 2, 3, 4]
.
The total scores for the potential sequences are:
- Total score for
[0, 1, 2, 3]
=20 + 10 + 30 + 40 = 100
- Total score for
[1, 2, 3, 4]
=10 + 30 + 40 + 25 = 105
Since we are looking for the maximum score, the best sequence is [1, 2, 3, 4]
with a total score of 105
.
Hence, the algorithm will return 105
as the maximum score of any valid node sequence with exactly 4 nodes for the given graph. If we could not find any valid sequence, we would return -1
, but in this case, we successfully found one.
Solution Implementation
1from collections import defaultdict
2from heapq import nlargest
3
4class Solution:
5 def maximumScore(self, scores: List[int], edges: List[List[int]]) -> int:
6 # Create a graph represented as an adjacency list
7 graph = defaultdict(list)
8
9 # Populate the graph with edges
10 for node_a, node_b in edges:
11 graph[node_a].append(node_b)
12 graph[node_b].append(node_a)
13
14 # Keep only the top 3 adjacent nodes with the highest scores for each node
15 for node in graph:
16 graph[node] = nlargest(3, graph[node], key=lambda x: scores[x])
17
18 # Initialize the maximum score to -1 (as a default for an impossible score)
19 max_score = -1
20
21 # Iterate through all edges to calculate possible scores
22 for node_a, node_b in edges:
23 # Check possible combinations with the top scoring adjacent nodes
24 for adjacent_from_a in graph[node_a]:
25 for adjacent_from_b in graph[node_b]:
26 # Ensure all nodes in the quadruple are distinct
27 if node_b != adjacent_from_a != adjacent_from_b != node_a:
28 # Calculate the total score of this quadruple
29 total_score = scores[node_a] + scores[node_b] + scores[adjacent_from_a] + scores[adjacent_from_b]
30 # Update the max score if the current total is greater
31 max_score = max(max_score, total_score)
32
33 # Return the highest score found (or -1 if no such quadruple is possible)
34 return max_score
35
1class Solution {
2
3 // Calculates the maximum score from a given set of scores and edges.
4 public int maximumScore(int[] scores, int[][] edges) {
5 // Number of vertices in the graph
6 int numVertices = scores.length;
7
8 // Create a graph using adjacency lists, where each list will only contain the top 3 highest scoring vertices
9 List<Integer>[] graph = new List[numVertices];
10 Arrays.setAll(graph, v -> new ArrayList<>());
11
12 // Build the adjacency list for each vertex
13 for (int[] edge : edges) {
14 int vertex1 = edge[0], vertex2 = edge[1];
15 graph[vertex1].add(vertex2);
16 graph[vertex2].add(vertex1);
17 }
18
19 // Sort and truncate each adjacency list to top 3 vertices based on scores
20 for (int i = 0; i < numVertices; ++i) {
21 graph[i].sort((vertexA, vertexB) -> scores[vertexB] - scores[vertexA]);
22 graph[i] = graph[i].subList(0, Math.min(3, graph[i].size()));
23 }
24
25 // Initialize the answer to -1, which means it's not possible to find a valid quadruple
26 int maxScore = -1;
27
28 // Loop through each edge to find the highest score by considering quadruples
29 for (int[] edge : edges) {
30 int vertexA = edge[0], vertexB = edge[1];
31 // Consider all pairs (vertexC, vertexD) where vertexC is connected to vertexA
32 // and vertexD is connected to vertexB, ensuring they are all different vertices.
33 for (int vertexC : graph[vertexA]) {
34 for (int vertexD : graph[vertexB]) {
35 if (vertexC != vertexB && vertexC != vertexD && vertexA != vertexD) {
36 int currentScore = scores[vertexA] + scores[vertexB] + scores[vertexC] + scores[vertexD];
37 maxScore = Math.max(maxScore, currentScore);
38 }
39 }
40 }
41 }
42
43 // Return the maximum score found, or -1 if no such quadruple exists
44 return maxScore;
45 }
46}
47
1#include <vector>
2#include <algorithm>
3#include <numeric>
4
5class Solution {
6public:
7 // Calculates the maximum score from a given set of scores and edges.
8 int maximumScore(std::vector<int>& scores, std::vector<std::vector<int>>& edges) {
9 // Number of vertices in the graph
10 int numVertices = scores.size();
11
12 // Graph representation using adjacency lists, keeping only the top 3 vertices sorted by their scores
13 std::vector<std::vector<int>> graph(numVertices);
14
15 // Build the adjacency list for each vertex from the edges
16 for (const auto& edge : edges) {
17 int vertex1 = edge[0], vertex2 = edge[1];
18 graph[vertex1].push_back(vertex2);
19 graph[vertex2].push_back(vertex1);
20 }
21
22 // Sort and keep only the top 3 vertices with highest scores in each adjacency list
23 for (int i = 0; i < numVertices; ++i) {
24 std::sort(graph[i].begin(), graph[i].end(), [&](int vertexA, int vertexB) {
25 return scores[vertexB] - scores[vertexA];
26 });
27
28 if (graph[i].size() > 3) {
29 graph[i].resize(3);
30 }
31 }
32
33 // Initialize the maximum score to -1 which signifies that it is not possible to find a valid quadruple
34 int maxScore = -1;
35
36 // Check all quadruples formed with the edges to calculate the maximum score
37 for (const auto& edge : edges) {
38 int vertexA = edge[0], vertexB = edge[1];
39 // Consider all pairs (vertexC, vertexD) where vertexC is connected to vertexA
40 // and vertexD is connected to vertexB, making sure they are all distinct.
41 for (int vertexC : graph[vertexA]) {
42 if (vertexC == vertexB) continue;
43 for (int vertexD : graph[vertexB]) {
44 if (vertexD == vertexA || vertexD == vertexC) continue;
45 int currentScore = scores[vertexA] + scores[vertexB] + scores[vertexC] + scores[vertexD];
46 maxScore = std::max(maxScore, currentScore);
47 }
48 }
49 }
50
51 // Return the maximum score found, or -1 if no valid quadruple exists.
52 return maxScore;
53 }
54};
55
1// Our function that calculates the maximum score
2function maximumScore(scores: number[], edges: number[][]): number {
3 // Number of vertices in the graph
4 const numVertices: number = scores.length;
5
6 // Create a graph using adjacency lists, where each list will only contain
7 // the top 3 highest scoring vertices connected to it
8 const graph: number[][] = new Array(numVertices).fill(0).map(() => []);
9
10 // Build the adjacency list for each vertex
11 for (const edge of edges) {
12 const [vertex1, vertex2] = edge;
13 graph[vertex1].push(vertex2);
14 graph[vertex2].push(vertex1);
15 }
16
17 // Sort and truncate each adjacency list to top 3 vertices based on scores
18 graph.forEach((adjList, index) => {
19 adjList.sort((vertexA, vertexB) => scores[vertexB] - scores[vertexA]);
20 graph[index] = graph[index].slice(0, Math.min(3, graph[index].length));
21 });
22
23 // Initialize the answer to -1, which means it's not possible to find a
24 // valid quadruple with the current set of edges
25 let maxScore: number = -1;
26
27 // Loop through each edge to find the highest score by considering quadruples
28 for (const edge of edges) {
29 const [vertexA, vertexB] = edge;
30 // Consider all pairs (vertexC, vertexD) where vertexC is connected to vertexA
31 // and vertexD is connected to vertexB, ensuring they are all unique vertices.
32 for (const vertexC of graph[vertexA]) {
33 for (const vertexD of graph[vertexB]) {
34 if (vertexC !== vertexB && vertexC !== vertexD && vertexA !== vertexD) {
35 const currentScore: number = scores[vertexA] + scores[vertexB] + scores[vertexC] + scores[vertexD];
36 maxScore = Math.max(maxScore, currentScore);
37 }
38 }
39 }
40 }
41
42 // Return the maximum score found, or -1 if no such quadruple exists
43 return maxScore;
44}
45
Time and Space Complexity
Time Complexity
The provided code has several components to consider in terms of time complexity:
-
Building the graph
g
: The first for loop goes through each edge in theedges
list, which runs O(E) times, where E is the number of edges given. -
Trimming the adjacency lists to the top 3 scores: For each node, the
nlargest
function is called, which takes O(m log n) time where m is the total number of elements to sort (in this case, the number of adjacent nodes) and n is the number of elements to find (3 in this case). Since we perform this operation for all nodesk
, and in the worst case, this could be for all nodes 'V', this step can be bounded by O(V * m log n), where V is the number of vertices. Since m can also be bounded by E in the worst case (if all edges are connected to one vertex), this step simplifies to O(E log 3), and since finding the largest 3 elements can be done in constant time, it simplifies to O(E). -
The nested loops to calculate the maximum score: This is the most computationally expensive part of the code. There is a nested four-loop structure here. However, due to the previous trimming of the adjacency lists, the third and fourth loops run at most 3 times each since they loop over the top 3 scores for vertices a and b, respectively. Thus, these loops contribute a constant factor, and the time complexity comes down to the double loop over the edges. Hence, the time complexity for this part is O(E).
Putting all of this together, the total time complexity is O(E + E log 3 + E) which can be simplified to O(E) because E is the dominant term and log 3 is a constant, and also because O(E log 3) simplifies to O(E) when log 3 is constant.
Space Complexity
The space complexity of the provided code is determined by:
-
The graph
g
which holds an adjacency list for each vertex. As we store only the 3 largest scores for each vertex, the space complexity is not directly proportional to the number of edges. Instead, it can be limited to O(V) where V is the number of vertices, as each vertex holds a list of at most 3 other vertices. -
The temp variables
t
andans
occupy O(1) space.
Hence, the total space complexity is O(V).
Learn more about how to find time and space complexity quickly using problem constraints.
Which data structure is used to implement recursion?
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
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
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
Want a Structured Path to Master System Design Too? Don’t Miss This!