题目描述
解法1:并查集
- 容易想到, 对于一棵树关键在于图中结点不能成环, 而对于连通性的判断, 很容易想到使用并查集
- 对于并查集, 先判断当前结点与其左右子结点的连通性, 若之前已经连通, 那么本次操作就会成环; 若不连通则进行连通操作
- 当所有结点操作完后, 在判断当前并查集的连通域数量,若为1, 则证明所有结点连通,即是一颗树
- 二叉是肯定二叉的,因为题目输入就直接是左右子树
class Solution {
//并查集
class UnionFind{
int *father;
int cnt;
public:
UnionFind(int n)
{
father = new int[n];
cnt = n;
for(int i = 0; i < n; i++)
father[i] = i;//初始化
}
int getFather(int x)
{
//路径压缩
if(father[x] != x) father[x] = getFather(father[x]);
return father[x];
}
void Union(int v1, int v2)//连通父子结点,v1为父,v2为子
{
int f1 = getFather(v1),f2 = getFather(v2);
if(f1 == f2) return;
father[v2] = v1;//v2连接到v1上
}
//判断是否连通
bool Is_connected(int v1, int v2)
{
return getFather(v1) == getFather(v2);
}
//计算并查集中集合的数量
int countRoot()
{
int num = 0;
for(int i = 0; i < cnt; i++)
if(father[i] == i)
num++;
return num;
}
~UnionFind(){delete[] father;}
};
public:
bool validateBinaryTreeNodes(int n, vector<int>& leftChild, vector<int>& rightChild) {
UnionFind uf(n);
for(int i = 0; i < n; i++)
{
if(leftChild[i] != -1)
{
//若之前已经连通则会成环返回false
if(uf.Is_connected(i, leftChild[i])) return false;
uf.Union(i, leftChild[i]);
}
if(rightChild[i] != -1)
{
if(uf.Is_connected(i, rightChild[i])) return false;
uf.Union(i, rightChild[i]);
}
}
if(uf.countRoot() == 1)
return true;
return false;
}
};
封装的思想是我借鉴的,值得学习
解法2:找到根结点 + DFS判断连通性
- 已知树的性质:除了根结点的入度是0外,其余每个结点的入度必须是1才可。也就是说入度为0的只能有一个,入度为2的不能有
- 但仅仅这样还不行
满足入度为0的只能有一个,入度为2的没有,但它不是树。所以还需要DFS(或者BFS、先中后序遍历、层序遍历都行)
class Solution {
public:
bool validateBinaryTreeNodes(int n, vector<int>& leftChild, vector<int>& rightChild) {
//除了根结点的入度是0外,其余每个结点的入度必须是1才可
//也就是入度为0的只能有一个,入度为2的不能有
vector<int> in(n);
for(int i=0; i<n; i++)
{
if(leftChild[i] != -1) in[leftChild[i]]++;
if(rightChild[i] != -1) in[rightChild[i]]++;
}
int cnt0 = 0,cnt2 = 0, root = -1;
for(int i=0; i<n; i++)
{
if(in[i] == 0) {cnt0++; root = i;}
if(in[i] > 1) cnt2++;
}
if(!(cnt0 == 1 && cnt2 == 0))
return false;
//DFS判断连通性
vector<int> visited(n);
DFS(visited, leftChild, rightChild, root);
for(int i : visited)
if(!i)
return false;
return true;
}
void DFS(vector<int>& visited, vector<int>& leftChild, vector<int>& rightChild, int v)
{
if(v==-1) return;
visited[v]=1;
DFS(visited, leftChild, rightChild, leftChild[v]);
DFS(visited, leftChild, rightChild, rightChild[v]);
}
};
另外,再附上先序遍历的代码
class Solution {
public:
bool validateBinaryTreeNodes(int n, vector<int>& leftChild, vector<int>& rightChild) {
//除了根结点的入度是0外,其余每个结点的入度必须是1才可
//也就是入度为0的只能有一个,入度为2的不能有
vector<int> in(n);
for(int i=0; i<n; i++)
{
if(leftChild[i] != -1) in[leftChild[i]]++;
if(rightChild[i] != -1) in[rightChild[i]]++;
}
int cnt0 = 0,cnt2 = 0, root = -1;
for(int i=0; i<n; i++)
{
if(in[i] == 0) {cnt0++; root = i;}
if(in[i] > 1) cnt2++;
}
if(!(cnt0 == 1 && cnt2 == 0))
return false;
//先序遍历判断连通性
stack<int> s;//辅助栈
s.push(root);
while(n && !s.empty())
{
root = s.top();
s.pop();
if(rightChild[root] != -1)
s.push(rightChild[root]);
if(leftChild[root] != -1)
s.push(leftChild[root]);
n--;
}
//若搜索次数少于n次或搜索n次后辅助栈还存在节点说明不是一颗二叉树
if (n != 0 || !s.empty())
return false;
return true;
}
};
最后发现,竟然是先序遍历的运行时间最短,难道是因为前两种有迭代?