LeetcodeLeetCode
Diameter of Binary Tree
Find the longest path between any two nodes in a binary tree
Calculate the length of the longest path between any two nodes in a binary tree
問題描述
給定一個二叉樹 root
,返回其 直徑 的長度。
二叉樹的 直徑 是指樹中任意兩個節點之間最長路徑的 長度。這條路徑可能經過也可能不經過根節點。
兩節點之間路徑的長度 是由它們之間邊的數目表示。
範例
輸入:root = [1,2,3,4,5]
輸出:3
解釋:直徑路徑是 [4,2,1,3] 或者 [5,2,1,3],長度為 3
輸入:root = [1,2]
輸出:1
限制條件
- 樹中節點數的範圍是
[1, 10⁴]
-100 <= Node.val <= 100
解題思路
直徑的定義
直徑 = 左子樹高度 + 右子樹高度
遞歸計算高度
在計算高度的同時更新最大直徑
全局維護最大值
使用全局變數追蹤最大直徑
解決方案
/**
* Definition for a binary tree node.
*/
export class TreeNode {
val: number;
left: TreeNode | null;
right: TreeNode | null;
constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
this.val = val === undefined ? 0 : val;
this.left = left === undefined ? null : left;
this.right = right === undefined ? null : right;
}
}
export function diameterOfBinaryTree(root: TreeNode | null): number {
// Track the maximum diameter
let maxDiameter = 0;
// Recursively calculate each node's height while updating max diameter
function calculateHeight(node: TreeNode | null): number {
// Base case: height of empty node is 0
if (node === null)
return 0;
// Recursively calculate left and right subtree heights
const leftHeight = calculateHeight(node.left);
const rightHeight = calculateHeight(node.right);
// Update maximum diameter (left subtree height + right subtree height)
maxDiameter = Math.max(maxDiameter, leftHeight + rightHeight);
// Return current node's height (max of left/right subtree + 1)
return Math.max(leftHeight, rightHeight) + 1;
}
calculateHeight(root);
return maxDiameter;
}
import { describe, expect, it } from 'vitest';
import { diameterOfBinaryTree, TreeNode } from './solution';
describe('543. Diameter of Binary Tree', () => {
function createTree(values: (number | null)[]): TreeNode | null {
if (!values.length)
return null;
const root = new TreeNode(values[0] as number);
const queue = [root];
let i = 1;
while (queue.length && i < values.length) {
const node = queue.shift()!;
// Left child
if (i < values.length && values[i] !== null) {
node.left = new TreeNode(values[i] as number);
queue.push(node.left);
}
i++;
// Right child
if (i < values.length && values[i] !== null) {
node.right = new TreeNode(values[i] as number);
queue.push(node.right);
}
i++;
}
return root;
}
it('should handle example case 1', () => {
const root = createTree([1, 2, 3, 4, 5]);
expect(diameterOfBinaryTree(root)).toBe(3);
});
it('should handle example case 2', () => {
const root = createTree([1, 2]);
expect(diameterOfBinaryTree(root)).toBe(1);
});
it('should handle empty tree', () => {
expect(diameterOfBinaryTree(null)).toBe(0);
});
it('should handle single node', () => {
const root = createTree([1]);
expect(diameterOfBinaryTree(root)).toBe(0);
});
it('should handle long path through root', () => {
const root = createTree([1, 2, 3, 4, null, null, 5]);
expect(diameterOfBinaryTree(root)).toBe(3);
});
});
高度與直徑的關係
這個問題結合了深度計算和直徑計算的技巧。
核心洞察
對於任意節點,經過該節點的最長路徑長度 = 左子樹高度 + 右子樹高度
算法步驟
- 遞歸計算高度:為每個節點計算其子樹高度
- 更新直徑:在每個節點計算經過它的最長路徑
- 維護最大值:使用全局變數追蹤最大直徑
複雜度分析
- 時間複雜度: O(n) - 每個節點訪問一次
- 空間複雜度: O(h) - h 是樹的高度,遞歸棧的深度
關鍵在於理解直徑不一定經過樹的根節點,可能在任意子樹中
執行過程示例
以樹 [1,2,3,4,5]
為例:
1
/ \
2 3
/ \
4 5
計算過程:
- 節點 4: 高度 = 1,直徑 = 0 + 0 = 0
- 節點 5: 高度 = 1,直徑 = 0 + 0 = 0
- 節點 2: 高度 = 2,直徑 = 1 + 1 = 2
- 節點 3: 高度 = 1,直徑 = 0 + 0 = 0
- 節點 1: 高度 = 3,直徑 = 2 + 1 = 3
最大直徑: 3(路徑:4-2-1-3 或 5-2-1-3)
邊界情況處理
- 空樹: 直徑為 0
- 單節點: 直徑為 0
- 鏈狀樹: 直徑為節點數 - 1
- 完全二叉樹: 直徑可能不經過根節點
重要的設計模式
這個問題展示了一個重要的模式:在遞歸過程中同時計算多個值。
每個遞歸函數調用都:
- 計算當前節點的高度(返回值)
- 更新全局的最大直徑(副作用)
類似問題的應用
- 最遠葉子節點: 尋找距離最遠的兩個葉子
- 樹的半徑: 從中心到最遠點的距離
- 最長路徑和: 路徑上所有節點值的最大和
- 最大路徑差: 路徑上最大值和最小值的差
優化版本
如果只需要返回直徑而不需要高度,可以將高度計算內嵌:
function diameterOptimized(root: TreeNode | null): number {
let maxDiameter = 0;
function dfs(node: TreeNode | null): number {
if (!node) return 0;
const left = dfs(node.left);
const right = dfs(node.right);
maxDiameter = Math.max(maxDiameter, left + right);
return Math.max(left, right) + 1;
}
dfs(root);
return maxDiameter;
}