Before attempting this problem, you should be comfortable with:
Graph Representation (Adjacency List) - Building and traversing graphs using adjacency lists to store edges and neighbors
Depth First Search (DFS) - Recursively exploring all reachable nodes in a graph while tracking visited nodes
Breadth First Search (BFS) - Level-by-level graph traversal using a queue
Union-Find (Disjoint Set Union) - Efficiently grouping nodes into connected components with path compression and union by rank
1. Depth First Search
Intuition
The problem asks for the minimum edge weight on any path from city 1 to city n, where we can revisit edges and nodes. The key realization is that since we can traverse any edge multiple times, we're essentially looking for the minimum edge weight in the entire connected component containing city 1. If an edge is reachable from city 1, we can always include it in our path by going there and back.
Algorithm
Build an adjacency list from the edges, storing both the neighbor and the edge distance.
Initialize res to infinity and a visited set.
Run DFS starting from node 1:
Mark the current node as visited.
For each neighbor, update res with the minimum of res and the edge distance.
classSolution:defminScore(self, n:int, roads:list[list[int]])->int:
adj = defaultdict(list)for src, dst, dist in roads:
adj[src].append((dst, dist))
adj[dst].append((src, dist))
res =float("inf")
visit =set()defdfs(node):nonlocal res
if node in visit:return
visit.add(node)for nei, dist in adj[node]:
res =min(res, dist)
dfs(nei)
dfs(1)return res
publicclassSolution{privateList<int[]>[] adj;privateboolean[] visit;privateint res;publicintminScore(int n,int[][] roads){
adj =newArrayList[n +1];
visit =newboolean[n +1];
res =Integer.MAX_VALUE;for(int i =0; i <= n; i++){
adj[i]=newArrayList<>();}for(int[] road : roads){
adj[road[0]].add(newint[]{road[1], road[2]});
adj[road[1]].add(newint[]{road[0], road[2]});}dfs(1);return res;}privatevoiddfs(int node){if(visit[node])return;
visit[node]=true;for(int[] edge : adj[node]){
res =Math.min(res, edge[1]);dfs(edge[0]);}}}
classSolution{/**
* @param {number} n
* @param {number[][]} roads
* @return {number}
*/minScore(n, roads){const adj = Array.from({length: n +1},()=>[]);for(const[src, dst, dist]of roads){
adj[src].push([dst, dist]);
adj[dst].push([src, dist]);}let res =Infinity;const visit =newArray(n +1).fill(false);constdfs=(node)=>{if(visit[node])return;
visit[node]=true;for(const[nei, dist]of adj[node]){
res = Math.min(res, dist);dfs(nei);}};dfs(1);return res;}}
publicclassSolution{privateList<int[]>[] adj;privatebool[] visit;privateint res;publicintMinScore(int n,int[][] roads){
adj =newList<int[]>[n +1];
visit =newbool[n +1];
res =int.MaxValue;for(int i =0; i <= n; i++){
adj[i]=newList<int[]>();}foreach(var road in roads){
adj[road[0]].Add(newint[]{ road[1], road[2]});
adj[road[1]].Add(newint[]{ road[0], road[2]});}Dfs(1);return res;}privatevoidDfs(int node){if(visit[node])return;
visit[node]=true;foreach(var edge in adj[node]){
res = Math.Min(res, edge[1]);Dfs(edge[0]);}}}
funcminScore(n int, roads [][]int)int{
adj :=make([][][2]int, n+1)for i :=range adj {
adj[i]=[][2]int{}}for_, road :=range roads {
adj[road[0]]=append(adj[road[0]],[2]int{road[1], road[2]})
adj[road[1]]=append(adj[road[1]],[2]int{road[0], road[2]})}
res :=1<<30
visit :=make([]bool, n+1)var dfs func(node int)
dfs =func(node int){if visit[node]{return}
visit[node]=truefor_, edge :=range adj[node]{if edge[1]< res {
res = edge[1]}dfs(edge[0])}}dfs(1)return res
}
class Solution {privatelateinitvar adj: Array<MutableList<IntArray>>privatelateinitvar visit: BooleanArray
privatevar res = Int.MAX_VALUE
funminScore(n: Int, roads: Array<IntArray>): Int {
adj =Array(n +1){ mutableListOf<IntArray>()}
visit =BooleanArray(n +1)
res = Int.MAX_VALUE
for(road in roads){
adj[road[0]].add(intArrayOf(road[1], road[2]))
adj[road[1]].add(intArrayOf(road[0], road[2]))}dfs(1)return res
}privatefundfs(node: Int){if(visit[node])return
visit[node]=truefor(edge in adj[node]){
res =minOf(res, edge[1])dfs(edge[0])}}}
classSolution{privatevar adj =[[(Int,Int)]]()privatevar visit =[Bool]()privatevar res =Int.max
funcminScore(_ n:Int,_ roads:[[Int]])->Int{
adj =[[(Int,Int)]](repeating:[], count: n +1)
visit =[Bool](repeating:false, count: n +1)
res =Int.max
for road in roads {
adj[road[0]].append((road[1], road[2]))
adj[road[1]].append((road[0], road[2]))}dfs(1)return res
}privatefuncdfs(_ node:Int){if visit[node]{return}
visit[node]=truefor(nei, dist)in adj[node]{
res =min(res, dist)dfs(nei)}}}
Time & Space Complexity
Time complexity: O(V+E)
Space complexity: O(V+E)
Where V is the number of vertices and E is the number of edges.
2. Breadth First Search
Intuition
BFS achieves the same goal as DFS by exploring all reachable nodes level by level. Since we need to find the minimum edge weight in the connected component containing node 1, BFS works equally well. We process each node, check all its edges, and track the smallest weight seen.
Algorithm
Build an adjacency list storing neighbors and distances.
Initialize res to infinity, a visited array, and a queue starting with node 1.
While the queue is not empty:
Dequeue a node.
For each neighbor, update res with the minimum edge distance.
If the neighbor hasn't been visited, mark it visited and enqueue it.
publicclassSolution{publicintMinScore(int n,int[][] roads){List<int[]>[] adj =newList<int[]>[n +1];for(int i =0; i <= n; i++) adj[i]=newList<int[]>();foreach(var road in roads){
adj[road[0]].Add(newint[]{ road[1], road[2]});
adj[road[1]].Add(newint[]{ road[0], road[2]});}int res =int.MaxValue;bool[] visit =newbool[n +1];Queue<int> q =newQueue<int>();
q.Enqueue(1);
visit[1]=true;while(q.Count >0){int node = q.Dequeue();foreach(var edge in adj[node]){int nei = edge[0], dist = edge[1];
res = Math.Min(res, dist);if(!visit[nei]){
visit[nei]=true;
q.Enqueue(nei);}}}return res;}}
funcminScore(n int, roads [][]int)int{
adj :=make([][][2]int, n+1)for i :=range adj {
adj[i]=[][2]int{}}for_, road :=range roads {
adj[road[0]]=append(adj[road[0]],[2]int{road[1], road[2]})
adj[road[1]]=append(adj[road[1]],[2]int{road[0], road[2]})}
res :=1<<30
visit :=make([]bool, n+1)
q :=[]int{1}
visit[1]=trueforlen(q)>0{
node := q[0]
q = q[1:]for_, edge :=range adj[node]{if edge[1]< res {
res = edge[1]}if!visit[edge[0]]{
visit[edge[0]]=true
q =append(q, edge[0])}}}return res
}
class Solution {funminScore(n: Int, roads: Array<IntArray>): Int {val adj =Array(n +1){ mutableListOf<IntArray>()}for(road in roads){
adj[road[0]].add(intArrayOf(road[1], road[2]))
adj[road[1]].add(intArrayOf(road[0], road[2]))}var res = Int.MAX_VALUE
val visit =BooleanArray(n +1)val q = ArrayDeque<Int>()
q.add(1)
visit[1]=truewhile(q.isNotEmpty()){val node = q.removeFirst()for(edge in adj[node]){val(nei, dist)= edge[0]to edge[1]
res =minOf(res, dist)if(!visit[nei]){
visit[nei]=true
q.add(nei)}}}return res
}}
classSolution{funcminScore(_ n:Int,_ roads:[[Int]])->Int{var adj =[[(Int,Int)]](repeating:[], count: n +1)for road in roads {
adj[road[0]].append((road[1], road[2]))
adj[road[1]].append((road[0], road[2]))}var res =Int.max
var visit =[Bool](repeating:false, count: n +1)var q =[1]
visit[1]=truewhile!q.isEmpty {let node = q.removeFirst()for(nei, dist)in adj[node]{
res =min(res, dist)if!visit[nei]{
visit[nei]=true
q.append(nei)}}}return res
}}
Time & Space Complexity
Time complexity: O(V+E)
Space complexity: O(V+E)
Where V is the number of vertices and E is the number of edges.
3. Iterative DFS
Intuition
This is the same approach as recursive DFS, but using an explicit stack instead of the call stack. This avoids potential stack overflow issues for very deep graphs and gives us more control over the traversal.
Algorithm
Build an adjacency list from the edges.
Initialize res to infinity, a visited array, and a stack with node 1.
While the stack is not empty:
Pop a node from the stack.
For each neighbor, update res with the minimum edge distance.
If the neighbor hasn't been visited, mark it visited and push it onto the stack.
publicclassSolution{publicintMinScore(int n,int[][] roads){List<int[]>[] adj =newList<int[]>[n +1];for(int i =0; i <= n; i++) adj[i]=newList<int[]>();foreach(var road in roads){
adj[road[0]].Add(newint[]{ road[1], road[2]});
adj[road[1]].Add(newint[]{ road[0], road[2]});}int res =int.MaxValue;bool[] visit =newbool[n +1];Stack<int> stack =newStack<int>();
stack.Push(1);
visit[1]=true;while(stack.Count >0){int node = stack.Pop();foreach(var edge in adj[node]){int nei = edge[0], dist = edge[1];
res = Math.Min(res, dist);if(!visit[nei]){
visit[nei]=true;
stack.Push(nei);}}}return res;}}
funcminScore(n int, roads [][]int)int{
adj :=make([][][2]int, n+1)for i :=range adj {
adj[i]=[][2]int{}}for_, road :=range roads {
adj[road[0]]=append(adj[road[0]],[2]int{road[1], road[2]})
adj[road[1]]=append(adj[road[1]],[2]int{road[0], road[2]})}
res :=1<<30
visit :=make([]bool, n+1)
stack :=[]int{1}
visit[1]=trueforlen(stack)>0{
node := stack[len(stack)-1]
stack = stack[:len(stack)-1]for_, edge :=range adj[node]{if edge[1]< res {
res = edge[1]}if!visit[edge[0]]{
visit[edge[0]]=true
stack =append(stack, edge[0])}}}return res
}
class Solution {funminScore(n: Int, roads: Array<IntArray>): Int {val adj =Array(n +1){ mutableListOf<IntArray>()}for(road in roads){
adj[road[0]].add(intArrayOf(road[1], road[2]))
adj[road[1]].add(intArrayOf(road[0], road[2]))}var res = Int.MAX_VALUE
val visit =BooleanArray(n +1)val stack = ArrayDeque<Int>()
stack.addLast(1)
visit[1]=truewhile(stack.isNotEmpty()){val node = stack.removeLast()for(edge in adj[node]){val(nei, dist)= edge[0]to edge[1]
res =minOf(res, dist)if(!visit[nei]){
visit[nei]=true
stack.addLast(nei)}}}return res
}}
classSolution{funcminScore(_ n:Int,_ roads:[[Int]])->Int{var adj =[[(Int,Int)]](repeating:[], count: n +1)for road in roads {
adj[road[0]].append((road[1], road[2]))
adj[road[1]].append((road[0], road[2]))}var res =Int.max
var visit =[Bool](repeating:false, count: n +1)var stack =[1]
visit[1]=truewhile!stack.isEmpty {let node = stack.removeLast()for(nei, dist)in adj[node]{
res =min(res, dist)if!visit[nei]{
visit[nei]=true
stack.append(nei)}}}return res
}}
Time & Space Complexity
Time complexity: O(V+E)
Space complexity: O(V+E)
Where V is the number of vertices and E is the number of edges.
4. Disjoint Set Union
Intuition
Union-Find provides another way to identify which edges belong to the same connected component as node 1. First, we union all nodes connected by edges. Then, we iterate through all edges and check if they belong to the same component as node 1. The minimum weight among those edges is our answer.
Algorithm
Initialize a DSU (Disjoint Set Union) structure with path compression and union by size.
For each edge, union the two endpoints.
Find the root of node 1.
Iterate through all edges:
If either endpoint has the same root as node 1, update res with the minimum edge weight.
publicclassDSU{privateint[] parent, size;publicDSU(int n){
parent =newint[n +1];
size =newint[n +1];for(int i =0; i <= n; i++){
parent[i]= i;
size[i]=1;}}publicintFind(int node){if(parent[node]!= node){
parent[node]=Find(parent[node]);}return parent[node];}publicboolUnion(int u,int v){int pu =Find(u), pv =Find(v);if(pu == pv)returnfalse;if(size[pu]>= size[pv]){
size[pu]+= size[pv];
parent[pv]= pu;}else{
size[pv]+= size[pu];
parent[pu]= pv;}returntrue;}}publicclassSolution{publicintMinScore(int n,int[][] roads){DSU dsu =newDSU(n);foreach(var road in roads){
dsu.Union(road[0], road[1]);}int res =int.MaxValue;int root = dsu.Find(1);foreach(var road in roads){if(dsu.Find(road[0])== root){
res = Math.Min(res, road[2]);}}return res;}}
type DSU struct{
parent, size []int}funcNewDSU(n int)*DSU {
parent :=make([]int, n+1)
size :=make([]int, n+1)for i :=range parent {
parent[i]= i
size[i]=1}return&DSU{parent, size}}func(d *DSU)Find(node int)int{if d.parent[node]!= node {
d.parent[node]= d.Find(d.parent[node])}return d.parent[node]}func(d *DSU)Union(u, v int)bool{
pu, pv := d.Find(u), d.Find(v)if pu == pv {returnfalse}if d.size[pu]>= d.size[pv]{
d.size[pu]+= d.size[pv]
d.parent[pv]= pu
}else{
d.size[pv]+= d.size[pu]
d.parent[pu]= pv
}returntrue}funcminScore(n int, roads [][]int)int{
dsu :=NewDSU(n)for_, road :=range roads {
dsu.Union(road[0], road[1])}
res :=1<<30
root := dsu.Find(1)for_, road :=range roads {if dsu.Find(road[0])== root {if road[2]< res {
res = road[2]}}}return res
}
classDSU(n: Int){privateval parent =IntArray(n +1){ it }privateval size =IntArray(n +1){1}funfind(node: Int): Int {if(parent[node]!= node){
parent[node]=find(parent[node])}return parent[node]}fununion(u: Int, v: Int): Boolean {val pu =find(u)val pv =find(v)if(pu == pv)returnfalseif(size[pu]>= size[pv]){
size[pu]+= size[pv]
parent[pv]= pu
}else{
size[pv]+= size[pu]
parent[pu]= pv
}returntrue}}class Solution {funminScore(n: Int, roads: Array<IntArray>): Int {val dsu =DSU(n)for(road in roads){
dsu.union(road[0], road[1])}var res = Int.MAX_VALUE
val root = dsu.find(1)for(road in roads){if(dsu.find(road[0])== root){
res =minOf(res, road[2])}}return res
}}
classDSU{privatevar parent:[Int]privatevar size:[Int]init(_ n:Int){
parent =Array(0...n)
size =[Int](repeating:1, count: n +1)}funcfind(_ node:Int)->Int{if parent[node]!= node {
parent[node]=find(parent[node])}return parent[node]}funcunion(_ u:Int,_ v:Int)->Bool{let pu =find(u), pv =find(v)if pu == pv {returnfalse}if size[pu]>= size[pv]{
size[pu]+= size[pv]
parent[pv]= pu
}else{
size[pv]+= size[pu]
parent[pu]= pv
}returntrue}}classSolution{funcminScore(_ n:Int,_ roads:[[Int]])->Int{let dsu =DSU(n)for road in roads {_= dsu.union(road[0], road[1])}var res =Int.max
let root = dsu.find(1)for road in roads {if dsu.find(road[0])== root {
res =min(res, road[2])}}return res
}}
Time & Space Complexity
Time complexity: O(V+(E∗α(V)))
Space complexity: O(V)
Where V is the number of vertices and E is the number of edges in the graph. α() is used for amortized complexity.
Common Pitfalls
Treating It as a Shortest Path Problem
Many people instinctively reach for Dijkstra's algorithm when they see "minimum" and "path" in the same problem. However, this problem asks for the minimum edge weight along any path, not the shortest total distance. Since you can revisit edges freely, you simply need to find the smallest edge weight in the entire connected component containing node 1.
Only Considering Direct Paths
Some solutions only examine edges that lie on simple paths from node 1 to node n. This misses the key insight that you can traverse any edge in the connected component by taking detours. The answer is the minimum weight among all edges reachable from node 1, regardless of whether they appear on a direct path to node n.
Forgetting to Build Bidirectional Edges
The graph is undirected, so each edge must be added in both directions when constructing the adjacency list. Failing to do this will cause your traversal to miss large portions of the graph, leading to incorrect results when the minimum edge is only reachable through a path that requires traversing an edge in the "reverse" direction.