1.建立二叉搜索树:
(1)父子关系表
输入格式:
5
0 2 (节点0是根,子节点是2)
2 1 (节点2子节点是1)
2 3 (节点2子节点是3)
1 4 (节点1子节点是4)
3 5 (节点3子节点是5)
建立一般树:
vector<vector<int>> g; // 邻接表
// 带权值
// typedef pair<int,int> PII;
// vector<vector<PII>> g;
void bulidTree(){
int n;
cin>>n;
for(int i=0;i<n;++i){
int a,b;
cin>>a>>b;
g[a].push_back(b);
// g[b].push_back(a); // 无向树
}
}
DFS遍历二叉树(需传递父节点,防止回头,树无回路,所以无需vis数组):
void dfs(int cur,int fa){
for(auto& nxt:g[cur]){
if(nxt==fa) continue;
dfs(nxt,cur);
}
}
一、遍历
1.套路
2.题目描述
3.学习经验
1. 2368.受限条件下可到达节点的数目(中等,学习优化思想)
2368. 受限条件下可到达节点的数目 - 力扣(LeetCode)
思想
1.现有一棵由 n 个节点组成的无向树,节点编号从 0 到 n - 1 ,共有 n - 1 条边。
给你一个二维整数数组 edges ,长度为 n - 1 ,其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。另给你一个整数数组 restricted 表示 受限 节点。
在不访问受限节点的前提下,返回你可以从节点 0 到达的 最多 节点数目_。_
注意,节点 0 不 会标记为受限节点。
2.我的思路是在dfs遍历时判断是否受限,但这样会增加遍历次数
3.优化思想是在建树时就判断是否受限,若两端都不受限才建立边
4.然后节点数目可以不用vis数组,而是递归返回
代码
class Solution {
public:
vector<vector<int>> g;
int res = 0;
set<int> st;
vector<bool> vis;
void dfs(int cur, int fa) {
if (!vis[cur]) {
++res;
vis[cur] = true;
}
for (auto& nxt : g[cur]) {
if (nxt == fa || st.count(nxt))
continue;
dfs(nxt, cur);
}
}
int reachableNodes(int n, vector<vector<int>>& edges,
vector<int>& restricted) {
g.resize(n);
for (auto& e : edges) {
int a = e[0], b = e[1];
g[a].push_back(b);
g[b].push_back(a);
}
for (int& x : restricted)
st.insert(x);
vis.assign(n, false);
dfs(0, -1);
return res;
}
};
优化代码:
class Solution {
public:
vector<vector<int>> g;
int res = 0;
set<int> st;
vector<bool> vis;
int dfs(int cur, int fa) {
int cnt=1;
for (auto& nxt : g[cur]) {
if (nxt == fa)
continue;
cnt+=dfs(nxt, cur);
}
return cnt;
}
int reachableNodes(int n, vector<vector<int>>& edges,
vector<int>& restricted) {
g.resize(n);
for (int& x : restricted)
st.insert(x);
for (auto& e : edges) {
int a = e[0], b = e[1];
if(!st.count(a) && !st.count(b)){ // 建树时判断
g[a].push_back(b);
g[b].push_back(a);
}
}
return dfs(0, -1);
}
};
2. 1466. 重新规划路线(中等)
思想
1.n 座城市,从 0 到 n-1 编号,其间共有 n-1 条路线。因此,要想在两座不同城市之间旅行只有唯一一条路线可供选择(路线网形成一颗树)。去年,交通运输部决定重新规划路线,以改变交通拥堵的状况。
路线用 connections 表示,其中 connections[i] = [a, b] 表示从城市 a 到 b 的一条有向路线。
今年,城市 0 将会举办一场大型比赛,很多游客都想前往城市 0 。
请你帮助重新规划路线方向,使每个城市都可以访问城市 0 。返回需要变更方向的最小路线数。
题目数据 保证 每个城市在重新规划路线方向后都能到达城市 0 。
代码
class Solution {
public:
vector<vector<int>> gIn, gOut;
int res = 0;
void dfs(int cur, int fa) {
for (auto& nxt : gOut[cur]) {
if (nxt == fa)
continue;
++res;
dfs(nxt, cur);
}
for (auto& nxt : gIn[cur]) {
if (nxt == fa)
continue;
dfs(nxt, cur);
}
}
int minReorder(int n, vector<vector<int>>& connections) {
gIn.resize(n);
gOut.resize(n);
for (auto& con : connections) {
int a = con[0], b = con[1];
gOut[a].push_back(b);
gIn[b].push_back(a);
}
dfs(0, -1);
return res;
}
};
二、自顶向下DFS
1.套路
2.题目描述
3.学习经验
1. 1376. 通知所有员工所需的时间(中等)
1376. 通知所有员工所需的时间 - 力扣(LeetCode)
思想
1.公司里有 n 名员工,每个员工的 ID 都是独一无二的,编号从 0 到 n - 1。公司的总负责人通过 headID 进行标识。
在 manager 数组中,每个员工都有一个直属负责人,其中 manager[i] 是第 i 名员工的直属负责人。对于总负责人,manager[headID] = -1。题目保证从属关系可以用树结构显示。
公司总负责人想要向公司所有员工通告一条紧急消息。他将会首先通知他的直属下属们,然后由这些下属通知他们的下属,直到所有的员工都得知这条紧急消息。
第 i 名员工需要 informTime[i] 分钟来通知它的所有直属下属(也就是说在 informTime[i] 分钟后,他的所有直属下属都可以开始传播这一消息)。
返回通知所有员工这一紧急消息所需要的 分钟数 。
代码
class Solution {
public:
vector<vector<int>> g;
int res = 0;
void dfs(int cur, int fa, int sumTime, vector<int>& informTime) {
res = max(res, sumTime);
sumTime += informTime[cur];
for (auto& nxt : g[cur]) {
if (nxt == fa)
continue;
dfs(nxt, cur, sumTime, informTime);
}
}
int numOfMinutes(int n, int headID, vector<int>& manager,
vector<int>& informTime) {
g.resize(n);
for (int i = 0; i < n; ++i) {
// ma[i]->i
if (manager[i] != -1)
g[manager[i]].push_back(i);
}
dfs(headID, -1, 0, informTime);
return res;
}
};
2. 3528. 单位转换I(中等)
思想
1.有 n 种单位,编号从 0 到 n - 1。给你一个二维整数数组 conversions,长度为 n - 1,其中 conversions[i] = [sourceUniti, targetUniti, conversionFactori] ,表示一个 sourceUniti 类型的单位等于 conversionFactori 个 targetUniti 类型的单位。
请你返回一个长度为 n 的数组 baseUnitConversion,其中 baseUnitConversion[i] 表示 一个 0 类型单位等于多少个 i 类型单位。由于结果可能很大,请返回每个 baseUnitConversion[i] 对 109 + 7 取模后的值。
代码
class Solution {
public:
typedef pair<int, int> PII;
vector<vector<PII>> g;
vector<int> res;
int n;
const int mod = 1e9 + 7;
typedef long long ll;
void dfs(int cur, int fa, ll ji) {
res[cur] = ji % mod;
for (auto& nxt : g[cur]) {
int nxtTar = nxt.first, nxtFac = nxt.second;
if (nxtTar == fa)
continue;
dfs(nxtTar, cur, ji * nxtFac % mod);
}
}
vector<int> baseUnitConversions(vector<vector<int>>& conversions) {
n = conversions.size() + 1;
g.resize(n);
res.resize(n);
for (auto& con : conversions) {
int sou = con[0], tar = con[1], fac = con[2];
g[sou].push_back({tar, fac});
}
dfs(0, -1, 1);
return res;
}
};
3. 1443. 收集树上所有苹果的最少时间(中等)
1443. 收集树上所有苹果的最少时间 - 力扣(LeetCode)
思想
1.给你一棵有 n 个节点的无向树,节点编号为 0 到 n-1 ,它们中有一些节点有苹果。通过树上的一条边,需要花费 1 秒钟。你从 节点 0 出发,请你返回最少需要多少秒,可以收集到所有苹果,并回到节点 0 。
无向树的边由 edges 给出,其中 edges[i] = [fromi, toi] ,表示有一条边连接 from 和 toi 。除此以外,还有一个布尔数组 hasApple ,其中 hasApple[i] = true 代表节点 i 有一个苹果,否则,节点 i 没有苹果。
代码
class Solution {
public:
vector<vector<int>> g;
typedef pair<int, bool> PIO;
PIO dfs(int cur,int fa, vector<bool>& hasApple) {
int cnt = 0;
bool isA = false;
for (auto& nxt : g[cur]) {
if(nxt==fa) continue;
auto tmp = dfs(nxt,cur, hasApple);
if (tmp.second) {
cnt += tmp.first + 2;
isA = true;
}
}
if (hasApple[cur])
isA = true;
return {cnt, isA};
}
int minTime(int n, vector<vector<int>>& edges, vector<bool>& hasApple) {
g.resize(n);
for (auto& e : edges) {
int a = e[0], b = e[1];
g[a].push_back(b);
g[b].push_back(a);
}
auto tmp = dfs(0,-1, hasApple);
return tmp.first;
}
};
三、自底向上DFS
1.套路
2.题目描述
3.学习经验
1. 690. 员工的重要性(中等)
思想
1.你有一个保存员工信息的数据结构,它包含了员工唯一的 id ,重要度和直系下属的 id 。
给定一个员工数组 employees,其中:
-
employees[i].id是第i个员工的 ID。 -
employees[i].importance是第i个员工的重要度。 -
employees[i].subordinates是第i名员工的直接下属的 ID 列表。
给定一个整数id表示一个员工的 ID,返回这个员工和他所有下属的重要度的 总和 。
2.注意:auto emp = mp[id];
这里如果id不存在,就会尝试插入一个默认构造的值,所以结构体得有默认构造,不能只有显式构造
代码
/*
// Definition for Employee.
class Employee {
public:
int id;
int importance;
vector<int> subordinates;
};
*/
class Solution {
public:
struct newEmployee {
int importance;
vector<int> subordinates;
// 得有默认构造
newEmployee() : importance(0), subordinates() {}
newEmployee(int _imp, vector<int> _sub)
: importance(_imp), subordinates(_sub) {}
};
map<int, newEmployee> mp;
int dfs(int id) {
int sum = 0;
auto emp = mp[id];
for (auto& nxt : emp.subordinates) {
sum += dfs(nxt);
}
sum += emp.importance;
return sum;
}
int getImportance(vector<Employee*> employees, int id) {
for (auto& emp : employees) {
mp[emp->id] = newEmployee(emp->importance, emp->subordinates);
}
return dfs(id);
}
};
2. 3249. 统计好节点的数目(中等)
思想
1.现有一棵 无向 树,树中包含 n 个节点,按从 0 到 n - 1 标记。树的根节点是节点 0 。给你一个长度为 n - 1 的二维整数数组 edges,其中 edges[i] = [ai, bi] 表示树中节点 ai 与节点 bi 之间存在一条边。
如果一个节点的所有子节点为根的 子树 包含的节点数相同,则认为该节点是一个 好节点 。
返回给定树中 好节点 的数量。
子树 指的是一个节点以及它所有后代节点构成的一棵树。
代码
class Solution {
public:
vector<vector<int>> g;
int n;
int res = 0;
int dfs(int cur, int fa) {
int pre = INT_MIN;
bool isG = true;
int sum = 1;
for (auto& nxt : g[cur]) {
if (nxt == fa)
continue;
int cnt = dfs(nxt, cur);
sum += cnt;
if (pre == INT_MIN)
pre = cnt;
else if (pre != cnt)
isG = false;
}
if (isG)
++res;
return sum;
}
int countGoodNodes(vector<vector<int>>& edges) {
n = edges.size() + 1;
g.resize(n);
for (auto& e : edges) {
int a = e[0], b = e[1];
g[a].push_back(b);
g[b].push_back(a);
}
dfs(0, -1);
return res;
}
};