Leetcode 851. Loud and Rich 这道题本身没有什么很特殊的地方,但是它引发了我对面试写算法题的一些想法和思考。
题意:
给你一个拓扑序列richer,给你一个安静值quiet。
对于每一个节点,找到拓扑序列严格在这个节点之后的最小的quiet值的节点。(无法排列拓扑关系的,则不是严格在节点之后的)。
看到题目第一眼,首先想到排一个拓扑序列,再对于每个节点,取拓扑序列后一段的最小值(和最小值代表的节点)。时间复杂度大概是 O ( N l o g N ) O(NlogN) O(NlogN)。但是稍微想一下就觉得好像有点难处理拓扑序的严格之后,因为如果本身这个拓扑图就是一颗树一样的形状,每一个末梢都会被单独处理。
然后就开始想怎么解决这个最坏的拓扑图,树结构。
一想到树,就很简单了,如果题目把拓扑图变成树机构,这题就变成了,找到这棵子树quiet值最小的节点编号。
拓扑图是有向图,这题就变成了,找到这个节点之后的子图的quiet最小的节点编号。
然后就可以逆向建图,倒着推断,有一种记忆化搜索的感觉。
第一种写法是这样的,维护了两个值,子图中最小的quiet值,一个是子图中最小的quiet值的节点编号。
class Solution {
public:
vector loudAndRich(vector>& richer, vector& quiet) {
const int n = quiet.size();
vector> g(n);
vector ans(n, -1);
for(const auto& e : richer) {
g[e[1]].push_back(e[0]);
}
for(int i = 0; i < n; i++) {
dfs(i, g, quiet, ans);
}
return ans;
}
pair dfs(int node, const vector>& g, const vector& quiet, vector& ans) {
if (ans[node] > 0)
return make_pair(quiet[ans[node]], ans[node]);
ans[node] = node;
int minNode = quiet[node];
for(auto& next : g[node]) {
auto [minNext, ansNext] = dfs(next, g, quiet, ans);
if (minNode > minNext) {
minNode = minNext;
ans[node] = ans[next];
}
}
return make_pair(minNode, quiet[ans[node]]);
}
};
后来琢磨了一下代码,发现可以不用维护最小值,因为最小值就是最小值节点编号的quiet值。
class Solution {
public:
vector loudAndRich(vector>& richer, vector& quiet) {
const int n = quiet.size();
vector> g(n);
vector ans(n, -1);
for(const auto& e : richer) {
g[e[1]].push_back(e[0]);
}
for(int i = 0; i < n; i++) {
dfs(i, g, quiet, ans);
}
return ans;
}
int dfs(int node, const vector>& g, const vector& quiet, vector& ans) {
if (ans[node] > 0)
return quiet[ans[node]];
ans[node] = node;
for(auto& next : g[node]) {
if (quiet[ans[node]] > dfs(next, g, quiet, ans)) {
ans[node] = ans[next];
}
}
return quiet[ans[node]];
}
};