每日算法刷题Day78:10.23:leetcode 一般树7道题,用时1h30min
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. 重新规划路线(中等)
1466. 重新规划路线 - 力扣(LeetCode)
思想
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]->iif (manager[i] != -1)g[manager[i]].push_back(i);}dfs(headID, -1, 0, informTime);return res;}
};
2. 3528. 单位转换I(中等)
3528. 单位转换 I - 力扣(LeetCode)
思想
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. 员工的重要性(中等)
690. 员工的重要性 - 力扣(LeetCode)
思想
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. 统计好节点的数目(中等)
3249. 统计好节点的数目 - 力扣(LeetCode)
思想
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;}
};
