今天我们来解决LeetCode第965题——判断单值二叉树(Univalued Binary Tree)。这个问题看似简单,但能很好地考察我们对二叉树遍历和递归思想的理解。
什么是单值二叉树?
单值二叉树是指树中所有节点值都相同的二叉树。换句话说,从根节点到每个叶子节点,整棵树的所有节点值都必须相等。这个概念在实际应用中可能出现在数据一致性检查、配置验证等场景。
问题输入输出
示例分析
示例1中的树[1,1,1,1,1,null,1]所有非空节点值都是1,所以返回true。而示例2中的树[2,2,2,5,2]有一个节点值为5,与其他节点值不同,因此返回false。
最直观的想法是:我们需要检查树中每个节点的值是否都相同。这自然引出了遍历整棵树的需求。二叉树的遍历方式主要有三种:
对于这个问题,遍历顺序其实并不重要,因为我们只需要确保所有节点值相同即可。
我们选择深度优先搜索(DFS)的递归实现,原因如下:
具体算法步骤:
该算法需要访问树中的每个节点一次,因此时间复杂度为O(n),其中n是树中节点数量。空间复杂度取决于递归调用栈的深度,最坏情况下(树退化为链表)为O(n),平均情况下为O(log n)。
首先我们看题目给出的二叉树节点定义:
c复制struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
这是一个标准的二叉树节点结构,包含:
c复制bool dfs(struct TreeNode* root, int target) {
if (!root) return true; // 空节点不影响单值性
if (root->val != target) return false; // 发现不同值立即返回
return dfs(root->left, target) && dfs(root->right, target); // 递归检查子树
}
这个辅助函数实现了核心逻辑:
注意:这里使用短路求值(&&操作符的特性),一旦左子树返回false就不会再检查右子树,提高了效率。
c复制bool isUnivalTree(struct TreeNode* root) {
if (!root) return true; // 处理空树情况
return dfs(root, root->val); // 以根节点值为目标开始检查
}
主函数做了两件事:
为了确保代码正确性,我们应该设计多种测试用例:
c复制// 示例1
[1,1,1,1,1,null,1] → true
// 示例2
[2,2,2,5,2] → false
c复制// 单节点树
[1] → true
// 左斜树
[1,1,null,1,null,1] → true
// 右斜树
[1,null,1,null,1,null,1] → true
// 包含不同值的树
[1,1,2] → false
c复制// 空树
[] → true(虽然题目约束节点数≥1)
// 大值测试
[99999,99999,99999] → true
// 混合值测试
[1,1,1,2,1] → false
虽然递归解法简洁,但了解迭代解法也很重要。我们可以使用栈来实现DFS:
c复制bool isUnivalTree(struct TreeNode* root) {
if (!root) return true;
int target = root->val;
struct TreeNode* stack[100];
int top = -1;
stack[++top] = root;
while (top >= 0) {
struct TreeNode* node = stack[top--];
if (node->val != target) return false;
if (node->right) stack[++top] = node->right;
if (node->left) stack[++top] = node->left;
}
return true;
}
这种解法避免了递归带来的栈溢出风险,适合处理深度很大的树。
我们也可以用队列实现BFS:
c复制bool isUnivalTree(struct TreeNode* root) {
if (!root) return true;
int target = root->val;
struct TreeNode* queue[100];
int front = 0, rear = 0;
queue[rear++] = root;
while (front < rear) {
struct TreeNode* node = queue[front++];
if (node->val != target) return false;
if (node->left) queue[rear++] = node->left;
if (node->right) queue[rear++] = node->right;
}
return true;
}
BFS按层遍历,适合处理宽度较大的树。
在递归解法中,我们可以添加提前终止的优化:
c复制bool dfs(struct TreeNode* root, int target) {
if (!root) return true;
if (root->val != target) return false;
// 如果左子树已经失败,不再检查右子树
if (!dfs(root->left, target)) return false;
return dfs(root->right, target);
}
这种优化在发现不一致时能尽早返回,减少不必要的计算。
新手常犯的错误是忘记检查空指针:
c复制// 错误示例
bool dfs(struct TreeNode* root, int target) {
if (root->val != target) return false; // 可能访问空指针
// ...
}
正确做法:总是先检查指针是否为NULL。
另一个常见错误是使用固定值而非根节点值作为目标:
c复制// 错误示例
bool isUnivalTree(struct TreeNode* root) {
return dfs(root, 0); // 错误:使用了固定值0
}
正确做法:应该使用root->val作为初始目标值。
不正确的终止条件会导致无限递归或错误结果:
c复制// 错误示例
bool dfs(struct TreeNode* root, int target) {
if (!root->left && !root->right) return true; // 仅叶子节点返回true
// ...
}
正确做法:应该在遇到NULL节点时返回true,遇到值不匹配时返回false。
单值二叉树检查可以应用于:
对于大规模树,可以考虑:
我在实际编码中发现,递归解法在大多数情况下已经足够高效。对于特别深的树(深度>1000),迭代解法更为安全。在LeetCode的测试用例中,递归解法通常运行时间在0-4ms之间,内存消耗在6-7MB左右。