Hard
Closest Node to Path in Tree — C++
Full explanation · Time O(n + q) · Space O(n + q)
// Time: O(n + q)
// Space: O(n + q)
// Tarjan's Offline LCA Algorithm
class Solution {
public:
vector<int> closestNode(int n, vector<vector<int>>& edges, vector<vector<int>>& query) {
vector<vector<int>> adj(n);
for (const auto& e : edges) {
adj[e[0]].emplace_back(e[1]), adj[e[1]].emplace_back(e[0]);
}
unordered_map<int, unordered_set<int>> pairs;
for (const auto& q : query) {
pairs[q[0]].emplace(q[1]), pairs[q[1]].emplace(q[0]);
pairs[q[0]].emplace(q[2]), pairs[q[2]].emplace(q[0]);
pairs[q[1]].emplace(q[2]), pairs[q[2]].emplace(q[1]);
}
TreeInfos tree_infos(adj, pairs);
vector<int> result;
for (const auto& q : query) {
vector<int> candidates = {tree_infos.lca(q[0], q[1]),
tree_infos.lca(q[0], q[2]),
tree_infos.lca(q[1], q[2])};
result.emplace_back(*max_element(cbegin(candidates), cend(candidates),
[&tree_infos](const auto& a, const auto& b) {
return tree_infos.depth(a) < tree_infos.depth(b);
}));
}
return result;
}
private:
class UnionFind {
public:
UnionFind(int n)
: set_(n)
, rank_(n)
, ancestor_(n) {
iota(set_.begin(), set_.end(), 0);
iota(ancestor_.begin(), ancestor_.end(), 0); // added
}
int find_set(int x) {
if (set_[x] != x) {
set_[x] = find_set(set_[x]); // Path compression.
}
return set_[x];
}
bool union_set(int x, int y) {
x = find_set(x), y = find_set(y);
if (x == y) {
return false;
}
if (rank_[x] > rank_[y]) {
swap(x, y);
}
set_[x] = y; // Union by rank.
if (rank_[x] == rank_[y]) {
++rank_[y];
}
return true;
}
int find_ancestor_of_set(int x) { // added
return ancestor_[find_set(x)];
}
void update_ancestor_of_set(int x) { // added
ancestor_[find_set(x)] = x;
}
private:
vector<int> set_;
vector<int> rank_;
vector<int> ancestor_; // added
};
class TreeInfos {
public:
TreeInfos(const vector<vector<int>>& adj, const unordered_map<int, unordered_set<int>>& pairs)
: D_(size(adj))
, uf_(size(adj))
, lookup_(size(adj)) {
dfs(adj, pairs, 0, -1);
}
int lca(int a, int b) const {
if (a > b) {
swap(a, b);
}
return lca_.at(a).at(b);
}
int depth(int a) const {
return D_[a];
}
private:
void dfs(const vector<vector<int>>& adj,
const unordered_map<int, unordered_set<int>>& pairs,
int curr, int parent) {
D_[curr] = (parent == -1) ? 1 : D_[parent] + 1;
for (const auto& child : adj[curr]) {
if (child == parent) {
continue;
}
dfs(adj, pairs, child, curr);
uf_.union_set(child, curr);
uf_.update_ancestor_of_set(curr);
}
lookup_[curr] = true;
if (!pairs.count(curr)) {
return;
}
for (const auto& v : pairs.at(curr)) {
if (!lookup_[v]) {
continue;
}
lca_[min(curr, v)][max(curr, v)] = uf_.find_ancestor_of_set(v);
}
}
vector<int> D_;
UnionFind uf_;
unordered_map<int, unordered_map<int, int>> lca_;
vector<bool> lookup_;
};
};
// Time: O(nlogn + qlogn)
// Space: O(nlogn)
// binary lifting (online lca algorithm)
class Solution2 {
public:
vector<int> closestNode(int n, vector<vector<int>>& edges, vector<vector<int>>& query) {
vector<vector<int>> adj(n);
for (const auto& e : edges) {
adj[e[0]].emplace_back(e[1]), adj[e[1]].emplace_back(e[0]);
}
TreeInfos tree_infos(adj);
vector<int> result;
for (const auto& q : query) {
vector<int> candidates = {tree_infos.lca(q[0], q[1]),
tree_infos.lca(q[0], q[2]),
tree_infos.lca(q[1], q[2])};
result.emplace_back(*max_element(cbegin(candidates), cend(candidates),
[&tree_infos](const auto& a, const auto& b) {
return tree_infos.depth(a) < tree_infos.depth(b);
}));
}
return result;
}
private:
class TreeInfos {
public:
TreeInfos(const vector<vector<int>>& adj)
: L_(size(adj))
, R_(size(adj))
, D_(size(adj))
, P_(size(adj))
, C_(-1) {
dfs(adj, 0, -1);
}
bool is_ancestor(int a, int b) const {
return L_[a] <= L_[b] && R_[b] <= R_[a];
}
int lca(int a, int b) const {
if (D_[a] > D_[b]) {
swap(a, b);
}
if (is_ancestor(a, b)) {
return a;
}
for (int i = size(P_[a]) - 1; i >= 0; --i) { // O(logN)
if (i < size(P_[a]) && !is_ancestor(P_[a][i], b)) {
a = P_[a][i];
}
}
return P_[a][0];
}
int depth(int a) const {
return D_[a];
}
private:
void dfs(const vector<vector<int>>& adj, int curr, int parent) {
D_[curr] = (parent == -1) ? 1 : D_[parent] + 1;
if (parent != -1) {
P_[curr].emplace_back(parent); // ancestors of the node i
}
for (int i = 0; i < size(P_[curr]); ++i) {
if (i >= size(P_[P_[curr][i]])) {
break;
}
P_[curr].emplace_back(P_[P_[curr][i]][i]);
}
L_[curr] = ++C_;
for (const auto& child : adj[curr]) {
if (child == parent) {
continue;
}
dfs(adj, child, curr);
}
R_[curr] = C_;
}
vector<int> L_;
vector<int> R_;
vector<int> D_;
vector<vector<int>> P_;
int C_;
};
};
// Time: O(n + q * h)
// Space: O(n)
// lca
class Solution3 {
public:
vector<int> closestNode(int n, vector<vector<int>>& edges, vector<vector<int>>& query) {
vector<vector<int>> adj(n);
for (const auto& e : edges) {
adj[e[0]].emplace_back(e[1]), adj[e[1]].emplace_back(e[0]);
}
TreeInfos tree_infos(adj);
vector<int> result;
for (const auto& q : query) {
vector<int> candidates = {tree_infos.lca(q[0], q[1]),
tree_infos.lca(q[0], q[2]),
tree_infos.lca(q[1], q[2])};
result.emplace_back(*max_element(cbegin(candidates), cend(candidates),
[&tree_infos](const auto& a, const auto& b) {
return tree_infos.depth(a) < tree_infos.depth(b);
}));
}
return result;
}
private:
class TreeInfos {
public:
TreeInfos(const vector<vector<int>>& adj)
: D_(size(adj))
, P_(size(adj)) {
dfs(adj, 0, -1);
}
int lca(int a, int b) const {
while (D_[a] > D_[b]) {
a = P_[a];
}
while (D_[a] < D_[b]) {
b = P_[b];
}
while (a != b) {
a = P_[a];
b = P_[b];
}
return a;
}
int depth(int a) const {
return D_[a];
}
private:
void dfs(const vector<vector<int>>& adj, int curr, int parent) {
D_[curr] = (parent == -1) ? 1 : D_[parent] + 1;
P_[curr] = parent; // ancestors of the node i
for (const auto& child : adj[curr]) {
if (child == parent) {
continue;
}
dfs(adj, child, curr);
}
}
vector<int> D_;
vector<int> P_;
};
};
// Time: O(n^2 + q * n)
// Space: O(n^2)
// bfs
class Solution4 {
public:
vector<int> closestNode(int n, vector<vector<int>>& edges, vector<vector<int>>& query) {
vector<vector<int>> adj(n);
for (const auto& e : edges) {
adj[e[0]].emplace_back(e[1]), adj[e[1]].emplace_back(e[0]);
}
const auto& bfs = [&adj](int root) {
vector<int> dist(size(adj), size(adj));
vector<int> q = {root};
dist[root] = 0;
for (int d = 0; !empty(q); ++d) {
vector<int> new_q;
for (const auto& u : q) {
for (const auto& v : adj[u]) {
if (d + 1 >= dist[v]) {
continue;
}
dist[v] = d + 1;
new_q.emplace_back(v);
}
}
q = move(new_q);
}
return dist;
};
vector<vector<int>> dist(n);
for (int i = 0; i < n; ++i) {
dist[i] = bfs(i);
}
vector<int> result;
for (const auto& q : query) {
int x = q[1];
for (int u = q[0]; u != q[1];) {
if (dist[q[2]][u] < dist[q[2]][x]) {
x = u;
}
for (const auto& v : adj[u]) {
if (dist[v][q[1]] < dist[u][q[1]]) {
u = v;
break;
}
}
}
result.emplace_back(x);
}
return result;
}
};
// Time: O(n^2 + q * n)
// Space: O(n^2)
// bfs
class Solution_TLE {
public:
vector<int> closestNode(int n, vector<vector<int>>& edges, vector<vector<int>>& query) {
vector<vector<int>> adj(n);
for (const auto& e : edges) {
adj[e[0]].emplace_back(e[1]), adj[e[1]].emplace_back(e[0]);
}
const auto& bfs = [&adj](int root) {
vector<int> dist(size(adj), size(adj));
vector<int> q = {root};
dist[root] = 0;
for (int d = 0; !empty(q); ++d) {
vector<int> new_q;
for (const auto& u : q) {
for (const auto& v : adj[u]) {
if (d + 1 >= dist[v]) {
continue;
}
dist[v] = d + 1;
new_q.emplace_back(v);
}
}
q = move(new_q);
}
return dist;
};
vector<vector<int>> dist(n);
for (int i = 0; i < n; ++i) {
dist[i] = bfs(i);
}
vector<int> result;
for (const auto& q : query) {
int j = -1;
for (int i = 0; i < n; ++i) {
if (dist[q[0]][q[2]] + dist[q[2]][q[1]] - 2 * dist[q[2]][i] == dist[q[0]][i] + dist[i][q[1]]) {
if (j == -1 || dist[q[2]][j] < dist[q[2]][i]) {
j = i;
}
}
}
result.emplace_back(j);
}
return result;
}
};