不了解递归的同学请先看【基础算法精讲 09】。
写一个自顶向下的 DFS,同时维护路径上的每个字母对应的最深节点
例如示例 1,递归到节点
对于节点
注意要写 回溯,递归返回前,要恢复
写一个自底向上的 DFS,返回子树大小。
当前子树大小为
⚠注意:在遍历列表的同时,往列表中加入数据,会导致我们遍历到新加入列表的数据。可以考虑固定遍历次数为
Go 语言不用考虑这个问题。
class Solution:
def findSubtreeSizes(self, parent: List[int], s: str) -> List[int]:
n = len(parent)
g = [[] for _ in range(n)]
for i in range(1, n):
g[parent[i]].append(i)
ancestor = defaultdict(lambda: -1)
def rebuild(x: int) -> None:
old = ancestor[s[x]]
ancestor[s[x]] = x
for i in range(len(g[x])):
y = g[x][i]
if (anc := ancestor[s[y]]) != -1:
g[anc].append(y)
g[x][i] = -1 # -1 表示删除 y
rebuild(y)
ancestor[s[x]] = old # 恢复现场
rebuild(0)
size = [1] * n # 注意这里已经把 1 算进去了
def dfs(x: int) -> None:
for y in g[x]:
if y != -1: # y 没被删除
dfs(y)
size[x] += size[y]
dfs(0)
return size
class Solution {
public int[] findSubtreeSizes(int[] parent, String s) {
int n = parent.length;
List<Integer>[] g = new ArrayList[n];
Arrays.setAll(g, i -> new ArrayList<>());
for (int i = 1; i < n; i++) {
g[parent[i]].add(i);
}
int[] ancestor = new int[26];
Arrays.fill(ancestor, -1);
rebuild(0, g, s.toCharArray(), ancestor);
int[] size = new int[n];
dfs(0, g, size);
return size;
}
private void rebuild(int x, List<Integer>[] g, char[] s, int[] ancestor) {
int sx = s[x] - 'a';
int old = ancestor[sx];
ancestor[sx] = x;
for (int i = g[x].size() - 1; i >= 0; i--) {
int y = g[x].get(i);
int anc = ancestor[s[y] - 'a'];
if (anc != -1) {
g[anc].add(y);
g[x].set(i, -1); // -1 表示删除 y
}
rebuild(y, g, s, ancestor);
}
ancestor[sx] = old; // 恢复现场
}
private void dfs(int x, List<Integer>[] g, int[] size) {
size[x] = 1;
for (int y : g[x]) {
if (y != -1) { // y 没被删除
dfs(y, g, size);
size[x] += size[y];
}
}
}
}
class Solution {
public:
vector<int> findSubtreeSizes(vector<int>& parent, string s) {
int n = parent.size();
vector<vector<int>> g(n);
for (int i = 1; i < n; i++) {
g[parent[i]].push_back(i);
}
int ancestor[26];
ranges::fill(ancestor, -1);
auto rebuild = [&](auto&& rebuild, int x) -> void {
int sx = s[x] - 'a';
int old = ancestor[sx];
ancestor[sx] = x;
for (int i = g[x].size() - 1; i >= 0; i--) {
int y = g[x][i];
int anc = ancestor[s[y] - 'a'];
if (anc != -1) {
g[anc].push_back(y);
g[x][i] = -1; // -1 表示删除 y
}
rebuild(rebuild, y);
}
ancestor[sx] = old; // 恢复现场
};
rebuild(rebuild, 0);
vector<int> size(n, 1); // 注意这里已经把 1 算进去了
auto dfs = [&](auto&& dfs, int x) -> void {
for (int y : g[x]) {
if (y != -1) { // y 没被删除
dfs(dfs, y);
size[x] += size[y];
}
}
};
dfs(dfs, 0);
return size;
}
};
func findSubtreeSizes(parent []int, s string) []int {
n := len(parent)
g := make([][]int, n)
for i := 1; i < n; i++ {
p := parent[i]
g[p] = append(g[p], i)
}
ancestor := [26]int{}
for i := range ancestor {
ancestor[i] = -1
}
var rebuild func(int)
rebuild = func(x int) {
sx := s[x] - 'a'
old := ancestor[sx]
ancestor[sx] = x
for i, y := range g[x] {
if anc := ancestor[s[y]-'a']; anc != -1 {
g[anc] = append(g[anc], y)
g[x][i] = -1 // -1 表示删除 y
}
rebuild(y)
}
ancestor[sx] = old // 恢复现场
}
rebuild(0)
size := make([]int, n)
var dfs func(int)
dfs = func(x int) {
size[x] = 1
for _, y := range g[x] {
if y != -1 { // y 没被删除
dfs(y)
size[x] += size[y]
}
}
}
dfs(0)
return size
}
- 时间复杂度:$\mathcal{O}(n+|\Sigma|)$,其中
是 的长度,$|\Sigma|=26$ 是字符集合的大小。 - 空间复杂度:$\mathcal{O}(n)$。
把两个 DFS 结合起来。在
- 如果
没有对应的祖先,把 增加 。 - 如果
有对应的祖先 ,把 增加 。
具体请看 视频讲解,欢迎点赞关注~
class Solution:
def findSubtreeSizes(self, parent: List[int], s: str) -> List[int]:
n = len(parent)
g = [[] for _ in range(n)]
for i in range(1, n):
g[parent[i]].append(i)
size = [1] * n
ancestor = defaultdict(lambda: -1)
def dfs(x: int) -> None:
old = ancestor[s[x]]
ancestor[s[x]] = x
for y in g[x]:
dfs(y)
anc = ancestor[s[y]]
size[x if anc < 0 else anc] += size[y]
ancestor[s[x]] = old # 恢复现场
dfs(0)
return size
class Solution {
public int[] findSubtreeSizes(int[] parent, String s) {
int n = parent.length;
List<Integer>[] g = new ArrayList[n];
Arrays.setAll(g, i -> new ArrayList<>());
for (int i = 1; i < n; i++) {
g[parent[i]].add(i);
}
int[] size = new int[n];
int[] ancestor = new int[26];
Arrays.fill(ancestor, -1);
dfs(0, g, s.toCharArray(), ancestor, size);
return size;
}
private void dfs(int x, List<Integer>[] g, char[] s, int[] ancestor, int[] size) {
size[x] = 1;
int sx = s[x] - 'a';
int old = ancestor[sx];
ancestor[sx] = x;
for (int y : g[x]) {
dfs(y, g, s, ancestor, size);
int anc = ancestor[s[y] - 'a'];
size[anc < 0 ? x : anc] += size[y];
}
ancestor[sx] = old; // 恢复现场
}
}
class Solution {
public:
vector<int> findSubtreeSizes(vector<int>& parent, string s) {
int n = parent.size();
vector<vector<int>> g(n);
for (int i = 1; i < n; i++) {
g[parent[i]].push_back(i);
}
vector<int> size(n, 1);
int ancestor[26];
ranges::fill(ancestor, -1);
auto dfs = [&](auto&& dfs, int x) -> void {
int sx = s[x] - 'a';
int old = ancestor[sx];
ancestor[sx] = x;
for (int y : g[x]) {
dfs(dfs, y);
int anc = ancestor[s[y] - 'a'];
size[anc < 0 ? x : anc] += size[y];
}
ancestor[sx] = old; // 恢复现场
};
dfs(dfs, 0);
return size;
}
};
func findSubtreeSizes(parent []int, s string) []int {
n := len(parent)
g := make([][]int, n)
for i := 1; i < n; i++ {
p := parent[i]
g[p] = append(g[p], i)
}
size := make([]int, n)
ancestor := [26]int{}
for i := range ancestor {
ancestor[i] = -1
}
var dfs func(int)
dfs = func(x int) {
size[x] = 1
sx := s[x] - 'a'
old := ancestor[sx]
ancestor[sx] = x
for _, y := range g[x] {
dfs(y)
anc := ancestor[s[y]-'a']
if anc < 0 {
anc = x
}
size[anc] += size[y]
}
ancestor[sx] = old // 恢复现场
}
dfs(0)
return size
}
- 时间复杂度:$\mathcal{O}(n+|\Sigma|)$,其中
是 的长度,$|\Sigma|=26$ 是字符集合的大小。 - 空间复杂度:$\mathcal{O}(n)$。
更多相似题目,见 链表、二叉树与一般树 中的「§2.2 自顶向下 DFS」和「§2.3 自底向上 DFS」。
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)