2277. Closest Node to Path in Tree
Problem Explanation
In this problem, we have a tree with n nodes numbered from 0 to n-1. We are given a 2D integer array edges that contains information about the bidirectional edges connecting the nodes of the tree. Our task is to answer a series of queries. Each query is represented as a 0-indexed integer array [start, end, node], and for each query, we must find the node on the path from start to end that is closest to node.
To do this, we first need to calculate the shortest path distances between nodes. Then we need to walk through the path from start to end while tracking the closest node, making sure to update it whenever we find a node that is closer to node.
Algorithm Explanation
-
First, we define a function
fillDistthat takes the current nodeu, distanced, and fills thedistarray with the shortest distance betweenuand all other nodes in the tree. To do this, we use a depth-first search approach, visiting all the neighbor nodesvof nodeuand callingfillDistrecursively withvand distanced+1. -
Next, we define a function
findClosestthat takes the current nodeu, destination nodeend, and target nodenode. It should return the node on the path fromutoendthat is closest tonode. We use a similar depth-first search approach here as well, iterating over the neighborsvof the nodeu. If the distance fromvtoendis smaller than the distance fromutoend, we update the closest node accordingly and callfindClosestrecursively withv. -
With these helper functions, we can now implement the function
closestNodethat takes input parametersn,edges, andquery. We first initialize an empty answer vectoransto store the results of each query. -
We create a
treerepresentation as an adjacency list using the information fromedges. -
We also initialize a 2D
distarray with dimensionsn x nand fill it with-1. Then, for each nodei, we callfillDistfunction to fill thedistarray with the shortest distance between nodeiand all other nodes in the tree. -
Then, we iterate over each query
q, extracting thestart,end, andnode. Afterwards, we call thefindClosestfunction withstart,end, andnodeto obtain the result for the current query. We store this result in our answer vectorans. -
Finally, we return the answer vector
anscontaining the results of all queries.
plaintext Example: n = 6 edges = [[0, 1], [0, 2], [1, 3], [1, 4], [2, 5]] query = [[1, 3, 2], [2, 0, 4]] Tree edges: 0 / \ 1 2 | | 3 5 | 4 Dist array: [[0 1 1 2 2 2] [1 0 2 1 1 3] [1 2 0 3 3 1] [2 1 3 0 2 4] [2 1 3 2 0 4] [2 3 1 4 4 0]] For query [1, 3, 2], distance from 1 to 3 is 1 so we return node 3 as it is closer to node 2. Ans = [3] For query [2, 0, 4], distance from 2 to 0 is 1 so we return node 0 due to smaller distance to node 4. Ans = [3, 0] Output: [3, 0]
Java Solution
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Solution {
public int[] closestNode(int n, int[][] edges, int[][] query) {
int[] ans = new int[query.length];
ArrayList<ArrayList<Integer>> tree = new ArrayList<>();
int[][] dist = new int[n][n];
for (int i = 0; i < n; i++) {
tree.add(new ArrayList<>());
Arrays.fill(dist[i], -1);
}
for (int[] edge : edges) {
int u = edge[0];
int v = edge[1];
tree.get(u).add(v);
tree.get(v).add(u);
}
for (int i = 0; i < n; i++) {
fillDist(tree, i, i, 0, dist);
}
for (int i = 0; i < query.length; i++) {
int start = query[i][0];
int end = query[i][1];
int node = query[i][2];
ans[i] = findClosest(tree, dist, start, end, node, start);
}
return ans;
}
private void fillDist(List<ArrayList<Integer>> tree, int start, int u, int d,
int[][] dist) {
dist[start][u] = d;
for (int v : tree.get(u))
if (dist[start][v] == -1)
fillDist(tree, start, v, d + 1, dist);
}
private int findClosest(List<ArrayList<Integer>> tree,
int[][] dist, int u, int end, int node,
int ans) {
for (int v : tree.get(u))
if (dist[v][end] < dist[u][end])
return findClosest(tree, dist, v, end, node,
dist[ans][node] < dist[v][node] ? ans : v);
return ans;
}
}
Python Solution
python from collections import defaultdict class Solution: def closestNode(self, n, edges, query): ans = [] tree = defaultdict(list) dist = [[-1] * n for _ in range(n)] for edge in edges: u, v = edge tree[u].append(v) tree[v].append(u) def fillDist(start, u, d): dist[start][u] = d for v in tree[u]: if dist[start][v] == -1: fillDist(start, v, d + 1) for i in range(n): fillDist(i, i, 0) def findClosest(u, end, node, ans): for v in tree[u]: if dist[v][end] < dist[u][end]: ans = findClosest(v, end, node, ans if dist[ans][node] < dist[v][node] else v) return ans for q in query: start, end, node = q ans.append(findClosest(start, end, node, start)) return ans
JavaScript Solution
javascript
class Solution {
closestNode(n, edges, query) {
const ans = [];
const tree = Array.from({ length: n }, () => []);
const dist = Array.from({ length: n }, () => Array(n).fill(-1));
for (const edge of edges) {
const [u, v] = edge;
tree[u].push(v);
tree[v].push(u);
}
const fillDist = (start, u, d) => {
dist[start][u] = d;
for (const v of tree[u]) {
if (dist[start][v] === -1) {
fillDist(start, v, d + 1);
}
};
};
for (let i = 0; i < n; i++) {
fillDist(i, i, 0);
}
const findClosest = (u, end, node, ans) => {
for (const v of tree[u]) {
if (dist[v][end] < dist[u][end]) {
ans = findClosest(v, end, node, dist[ans][node] < dist[v][node] ? ans : v);
}
}
return ans;
};
for (let i = 0; i < query.length; i++) {
const [start, end, node] = query[i];
ans.push(findClosest(start, end, node, start));
}
return ans;
}
}
C++ Solution
cpp
#include <algorithm>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> closestNode(int n, vector<vector<int>>& edges,
vector<vector<int>>& query) {
vector<int> ans;
vector<vector<int>> tree(n);
vector<vector<int>> dist(n, vector<int>(n, -1));
for (const vector<int>& edge : edges) {
const int u = edge[0];
const int v = edge[1];
tree[u].push_back(v);
tree[v].push_back(u);
}
for (int i = 0; i < n; ++i)
fillDist(tree, i, i, 0, dist);
for (const vector<int>& q : query) {
const int start = q[0];
const int end = q[1];
const int node = q[2];
ans.push_back(findClosest(tree, dist, start, end, node, start));
}
return ans;
}
private:
void fillDist(const vector<vector<int>>& tree, int start, int u, int d,
vector<vector<int>>& dist) {
dist[start][u] = d;
for (const int v : tree[u])
if (dist[start][v] == -1)
fillDist(tree, start, v, d + 1, dist);
}
int findClosest(const vector<vector<int>>& tree,
const vector<vector<int>>& dist, int u, int end, int node,
int ans) {
for (const int v : tree[u])
if (dist[v][end] < dist[u][end])
return findClosest(tree, dist, v, end, node,
dist[ans][node] < dist[v][node] ? ans : v);
return ans;
}
};
C# Solution
csharp
using System;
using System.Collections.Generic;
public class Solution {
public int[] ClosestNode(int n, int[][] edges, int[][] query) {
int[] ans = new int[query.Length];
List<int>[] tree = new List<int>[n];
int[][] dist = new int[n][];
for (int i = 0; i < n; i++) {
tree[i] = new List<int>();
dist[i] = new int[n];
Array.Fill(dist[i], -1);
}
foreach (int[] edge in edges) {
int u = edge[0];
int v = edge[1];
tree[u].Add(v);
tree[v].Add(u);
}
for (int i = 0; i < n; i++) {
FillDist(tree, i, i, 0, dist);
}
for (int i = 0; i < query.Length; i++) {
int start = query[i][0];
int end = query[i][1];
int node = query[i][2];
ans[i] = FindClosest(tree, dist, start, end, node, start);
}
return ans;
}
private void FillDist(List<int>[] tree, int start, int u, int d,
int[][] dist) {
dist[start][u] = d;
foreach (int v in tree[u]) {
if (dist[start][v] == -1) {
FillDist(tree, start, v, d + 1, dist);
}
};
}
private int FindClosest(List<int>[] tree,
int[][] dist, int u, int end, int node,
int ans) {
foreach (int v in tree[u]) {
if (dist[v][end] < dist[u][end]) {
ans = FindClosest(tree, dist, v, end, node,
dist[ans][node] < dist[v][node] ? ans : v);
}
}
return ans;
}
}
Time Complexity
The time complexity of our solution is O(n^2), where n is the number of nodes in the tree. This is because we perform depth-first searches on the tree for every node to compute the dist array.
Space Complexity
The space complexity of the solution is O(n^2) because of the distance matrix that stores the shortest distance between each pair of nodes in the tree. In addition to that, we have the adjacency list representation of the tree which takes O(n) space.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorConsider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is
[3, 7, 40, 80].
What is the recurrence relation?
Recommended Readings
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
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 assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!