leetcode
📊 解题思路
完全二叉树的特性(最后一层节点靠左排列)可优化节点计数:
- 高度计算:计算左右子树的最左路径深度(高度)
- 满二叉树判定:
- 左子树高度 = 右子树高度 → 左子树为满二叉树
- 左子树高度 > 右子树高度 → 右子树为满二叉树
- 公式优化:满子树直接通过公式
2^h - 1 计算节点数,避免递归遍历
时间复杂度从普通遍历的 O(n) 优化至 O(log²n)
🧩 关键步骤
- 终止条件:节点为空时返回0
- 高度计算:
- 左子树高度:从根节点一直向左遍历的深度
- 右子树高度:从根节点一直向左遍历的深度(非向右)
- 分支决策:
💻 代码实现
func countNodes(root *TreeNode) int {
if root == nil {
return 0
}
leftHeight := getHeight(root.Left)
rightHeight := getHeight(root.Right)
if leftHeight == rightHeight {
return (1 << leftHeight) + countNodes(root.Right)
}
return (1 << rightHeight) + countNodes(root.Left)
}
// 辅助函数:向左遍历计算高度
func getHeight(node *TreeNode) int {
height := 0
for node != nil {
height++
node = node.Left
}
return height
}
🔍 代码解析
-
getHeight函数:
- 沿左子树遍历至叶子节点
- 返回路径长度(例:节点
2在示例1中高度=2)
- 主逻辑:
- 计算左右子树高度(均向左遍历)
- 高度相等时,左子树满
(1 << leftHeight) 计算左子树+根节点数(公式:2ʰ)
- 递归计算右子树节点
- 第17行:高度不等时,右子树满
(1 << rightHeight) 计算右子树+根节点数
- 递归计算左子树节点
✅ 示例测试
func main() {
root1 := &TreeNode{Val: 1}
root1.Left = &TreeNode{Val: 2}
root1.Right = &TreeNode{Val: 3}
root1.Left.Left = &TreeNode{Val: 4}
root1.Left.Right = &TreeNode{Val: 5}
root1.Right.Left = &TreeNode{Val: 6}
fmt.Println(countNodes(root1)) // 6
fmt.Println(countNodes(nil)) // 0
root3 := &TreeNode{Val: 1}
fmt.Println(countNodes(root3)) // 1
}
⏱ 复杂度分析
| 指标 | 普通遍历 | 优化算法 |
| 时间复杂度 |
O(n) |
O(log²n) |
| 空间复杂度 |
O(log n) |
O(log n) |
推导过程:
- 单次递归:高度计算耗时 O(log n)
- 递归深度:树高 O(log n)
- 总耗时:O(log n) × O(log n) = O(log²n)