class Solution {
public:
bool validateBinaryTreeNodes(int n, vector<int>& leftChild, vector<int>& rightChild) {
}
};
1361. 验证二叉树
二叉树上有 n
个节点,按从 0
到 n - 1
编号,其中节点 i
的两个子节点分别是 leftChild[i]
和 rightChild[i]
。
只有 所有 节点能够形成且 只 形成 一颗 有效的二叉树时,返回 true
;否则返回 false
。
如果节点 i
没有左子节点,那么 leftChild[i]
就等于 -1
。右子节点也符合该规则。
注意:节点没有值,本问题中仅仅使用节点编号。
示例 1:
输入:n = 4, leftChild = [1,-1,3,-1], rightChild = [2,-1,-1,-1] 输出:true
示例 2:
输入:n = 4, leftChild = [1,-1,3,-1], rightChild = [2,3,-1,-1] 输出:false
示例 3:
输入:n = 2, leftChild = [1,0], rightChild = [-1,-1] 输出:false
示例 4:
输入:n = 6, leftChild = [1,-1,-1,4,-1,-1], rightChild = [2,-1,-1,5,-1,-1] 输出:false
提示:
1 <= n <= 10^4
leftChild.length == rightChild.length == n
-1 <= leftChild[i], rightChild[i] <= n - 1
原站题解
golang 解法, 执行用时: 36 ms, 内存消耗: 6.6 MB, 提交时间: 2023-06-13 10:06:10
func validateBinaryTreeNodes(n int, leftChild []int, rightChild []int) bool { inc := make([]int, n) // 记录每个结点的入度 for i := 0; i < n; i++ { // 开始遍历左右子树数组 // 如果i结点的左子树或者右子树是自身,那是不行的,可以直接返回false if leftChild[i] == i || rightChild[i] == i { return false } if n := leftChild[i]; n != -1 { // 开始填充每个结点的入度 inc[n]++ } if n := rightChild[i]; n != -1 { // 同上 inc[n]++ } } zeroc := 0 // 入度为0的个数(正常树的话,这个结点最后的值应该是1) for _, n := range inc { // 如果有入度大于2的结点或者入度为0的结点不止一个,那么可以直接返回false了 if n > 1 || zeroc > 1 { return false } if n == 0 { // 入度为0, zeroc++ } } if zeroc != 1 { // 入度为0的结点不止一个,直接返回false return false } // 代码到这,说明现在已经满足了 入度为0的结点只有一个,剩余n-1个结点的入度都为1 // 但是,还不能轻易返回,因为可能会有环, // 例如:leftChild=[1,0,3,-1], rightChild=[-1,-1,-1,-1] // +--+0 2 // | ^ + // | | | // v | v // 1 +-+ 3 // 所以下面,我们需要检测一下是否有环 // 首先,我们需要把着两个数组合并一下,把非-1的值合并到一个数组上。为什么要这样做呢? // 可知leftChild=[1,0,3,-1], rightChild=[-1,-1,-1,-1],这个是有环的 // 那么leftChild=[1,-1,3,-1], rightChild=[-1,0,-1,-1],这个也是有环的, // 但是因为leftChild[1]处的0跑到rightChild[1]处去了,导致我们不好检测 for i := 0; i < n; i++ { inc[i] = leftChild[i] if inc[i] == -1 && rightChild[i] != -1 { inc[i] = rightChild[i] } } // 我们复用inc数组,合并之后inc = [1,0,3,-1]了 // 从每一个结点出发, 进行环路检测。我们每遍历一个值会将其置为一个负数,标记已经遍历 for i := 0; i < n; i++ { if inc[i] < 0 { // 已经遍历过,那么从下一个结点再出发 continue } j := i // 当前结点没遍历过,那么从当前结点出发 for inc[j] >= 0 { // 如果访问到的结点大于0, tmp := j j = inc[j] // 那么让j去往下一个子结点 inc[tmp] = -(i+2) // 当前结点置一下访问标记 // 这里-(i+2)为第i轮的访问标记,为什么要区分轮次呢? // 因为如果不区分轮次,统一用负值标记,就会导致这么一个情况 // - 假设第1轮的1号结点是非根结点,那么在遍历之后会将其置为一个负值,假设为-2; // - 那么如果第2轮的2号结点是1号结点的父节点,它访问到1号结点时发现1号结点已经被访问过,那么会做出错误判断有环。 // 其实1号结点是先访问的,两次访问都不是在同一轮里。 } if inc[j] == -(i+2) { // 用当前轮次的访问标记判断一下当前j结点是否已经访问过。 return false // 如果当前轮次,该结点已经访问过,说明有环,直接返回false } } return true // 检查都无误后,返回true }
golang 解法, 执行用时: 380 ms, 内存消耗: 8.4 MB, 提交时间: 2023-06-13 10:05:46
/** * 递归标记 * 因为有n个结点,编号0~n-1,那么我们可以从这个n个结点分别出发,进行递归遍历。 * 每次遍历都用一个长度为n标记数组记录一下某个结点被访问到的次数, * 一旦在遍历过程中,发现某个结点被访问的次数大于1,说明有环,那么直接返回false; * 遍历完成后,检查标记数组的每个值,有且仅有一个下标的值为0, * 也就是被访问次数为0(该下标对应结点为根节点),那么就可以返回true。 */ func validateBinaryTreeNodes(n int, leftChild []int, rightChild []int) bool { res := true // dontCheck顾名思义,不需要再检查了,标识着已经有结点有大于1的入度, // 那么不用检查可以直接返回false了 dontCheck = false for i := 0; i < n; i++ { // 每个结点都出发遍历一次 res = true // 标记数组,记录每个结点被遍历的次数。因为每次这个数组都需要清空,我这里直接创新的了 visited = make([]int, n) check(i, leftChild, rightChild) // 递归 if dontCheck { // 如果dontCheck为true,那么可以直返返回false了 res = false // 置结果为false,break break } for _, n := range visited { // 检查一下标记数组的里面的值 // 如果这次遍历有没有访问到的结点,说明这个结构有可能不是树, //也有可能因为我们没有从根节点出发,导致根节点没有访问到 if n == 0 { // 我们没法判断,所以还是把res置为false,继续下一轮遍历 res = false break } } // 如果res为true,说明我们从i这个地方遍历,所有的结点都被我们访问了一次, // 那么这个结点就是树了,马上跳出循环返回true if res { break } } return res } var visited []int var dontCheck bool func check(i int, leftChild, rightChild []int){ // 如果已经dontCheck了,那么不需要继续递归了,直接返回。 // 如果visited[i]已经被访问过一次了,现在又访问到,说明这个结点入度大于1了,也需要直接返回 if dontCheck || visited[i] == 1 { dontCheck = true return } visited[i] = 1 // 标记一下,我们访问了i结点 if leftChild[i] != -1 { // 如果i结点有左子树,递归访问左子树 check(leftChild[i], leftChild, rightChild) } if rightChild[i] != -1 { // 如果i结点有右子树,递归访问右子树 check(rightChild[i], leftChild, rightChild) } }
java 解法, 执行用时: 13 ms, 内存消耗: 43.5 MB, 提交时间: 2023-06-13 10:03:59
class Solution { // 并查集用的集合列表 List<Integer> p = new ArrayList<>(); // 用于统计不相交的连通分支个数 int cnt; public boolean validateBinaryTreeNodes(int n, int[] leftChild, int[] rightChild) { // 用于标记各个孩子的父节点 int[] father = new int[n]; // 初始化 Arrays.fill(father, -1); // 初始化并查集集合状态 for(int i = 0; i < n; i++) p.add(i); // 初始化分支数 cnt = n; // 遍历所有节点 for(int i = 0; i < n; i++) { // 如果节点存在两个孩子,而且两个孩子相同,那么显然是错误的二叉树 if(leftChild[i] == rightChild[i] && leftChild[i] != -1) return false; // 合并两个孩子 if(!merge(father, i, leftChild[i]) || !merge(father, i, rightChild[i])) return false; } // 如果最后所有的节点组成一个连通分支,才是一棵树 if(cnt == 1) return true; return false; } // 和并父亲和孩子节点,并判断逻辑 private boolean merge(int[] father, int f, int c) { // 孩子是空的,直接返回 if(c == -1) return true; // 孩子之前有爸爸了,就是错的 if(father[c] != -1) return false; // 并查集查找两个集合的根 int a = find(f), b = find(c); // 如果孩子和父亲已经存在于一个集合中,那么说明会产生环,返回错误 if(a == b) return false; // 合并两个集合 p.set(a, b); // 标记孩子的父亲是谁 father[c] = f; // 连通分支数减一 cnt --; return true; } // 并查集通用方法,找集合的根元素 private int find(int x) { if(p.get(x) != x) { p.set(x, find(p.get(x))); } return p.get(x); } }
cpp 解法, 执行用时: 44 ms, 内存消耗: 35.7 MB, 提交时间: 2023-06-13 10:03:22
class Solution { public: bool validateBinaryTreeNodes(int n, vector<int>& leftChild, vector<int>& rightChild) { vector<int> indeg(n); for (int i = 0; i < n; ++i) { if (leftChild[i] != -1) { ++indeg[leftChild[i]]; } if (rightChild[i] != -1) { ++indeg[rightChild[i]]; } } int root = -1; for (int i = 0; i < n; ++i) { if (!indeg[i]) { root = i; break; } } if (root == -1) { return false; } unordered_set<int> seen; queue<int> q; seen.insert(root); q.push(root); while (!q.empty()) { int u = q.front(); q.pop(); if (leftChild[u] != -1) { if (seen.count(leftChild[u])) { return false; } seen.insert(leftChild[u]); q.push(leftChild[u]); } if (rightChild[u] != -1) { if (seen.count(rightChild[u])) { return false; } seen.insert(rightChild[u]); q.push(rightChild[u]); } } return seen.size() == n; } };
python3 解法, 执行用时: 72 ms, 内存消耗: 18 MB, 提交时间: 2023-06-13 10:03:02
class Solution: def validateBinaryTreeNodes(self, n: int, leftChild: List[int], rightChild: List[int]) -> bool: indeg = [0] * n for u in leftChild: if u != -1: indeg[u] += 1 for u in rightChild: if u != -1: indeg[u] += 1 root = -1 for i in range(n): if indeg[i] == 0: root = i break if root == -1: return False seen = set([root]) q = collections.deque([root]) while len(q) > 0: u = q.popleft() if leftChild[u] != -1: if leftChild[u] in seen: return False seen.add(leftChild[u]) q.append(leftChild[u]) if rightChild[u] != -1: if rightChild[u] in seen: return False seen.add(rightChild[u]) q.append(rightChild[u]) return len(seen) == n