输入:
二叉搜索树的根节点 root,以及两个整数 low 和 high。
要求:
计算树中所有值在 [low, high] 范围内的节点值之和。
输出:
一个整数,表示范围和。
思路:
这道题表面是树的遍历,内里藏着 二叉搜索树(BST) 最核心的“降维打击”思维。
1. 核心公式:BST + 中序遍历 = 有序数组
我们必须把这个公式刻在脑子里:二叉搜索树的中序遍历结果,就是严格递增的有序数组。
当你面对一棵复杂的二维 BST 时,只要在脑海中对它做一次中序遍历,它立刻就“坍缩”成了一个简单的一维有序数组。
- 本题要求的
[low, high]范围和,本质上就是在问:在这个隐形的有序数组中,截取中间符合范围的一段进行求和。
2. 为什么这就叫“降维打击”?
一旦你理解了上述本质,BST 的问题就全部变成了简单的数组问题。以下是四大经典场景(全是 LeetCode 高频题),你会发现它们本质上都是同一道题:
- 场景一:验证合法性 (LC.98)
- 数组视角:一个合法的有序数组必须是严格递增的。
- 解法:中序遍历,只要检查
current->val > prev->val。一旦出现乱序(逆序),直接返回 false。
- 场景二:找第 K 小 (LC.230)
- 数组视角:有序数组里第 K 个数是谁?
- 解法:中序遍历自带排序,只需要一个计数器
count,数到第K个直接返回,连数组都不用存。
- 场景三:恢复错乱数据 (LC.99)
- 数组视角:有序排队的队伍里,有两个人的位置站反了(逆序对)。
- 解法:中序遍历找“逆序对”。第一次遇到前大后小记录“大个子”,第二次遇到记录“小个子”,交换即可。
- 场景四:累加树 (LC.538)
- 数组视角:要把数组变成前缀和(或后缀和)。
- 解法:反向中序遍历(右->根->左)。这就相当于从数组最大的屁股后面往前遍历,一路累加,降维打击。
3. 回到本题 (LC.938)
本题就是上述思想的**“剪枝版”**。我们不需要真的把整个数组遍历出来,因为我们知道数组的头(小于 low)和尾(大于 high)是我们不需要的。
- 当前节点 < low:说明我们在有序数组的左侧,太小了,往右走(相当于抛弃数组左半边)。
- 当前节点 > high:说明我们在有序数组的右侧,太大了,往左走(相当于抛弃数组右半边)。
- 命中范围:说明我们在数组中间的有效段,累加并继续探索。
复杂度:
- 时间复杂度:O(N)
- 在最坏情况下需要遍历所有节点,但利用“有序数组”的思维进行剪枝,往往能屏蔽掉大量无效节点。
- 空间复杂度:O(H)
- 递归栈的深度。
class Solution {
public:
// 这里的 DFS 实际上就是在模拟在一个“有序数组”中筛选数据的过程
void dfs(TreeNode* node, int low, int high, int& ans) {
if (!node) {
return;
}
// 1. 如果当前节点在 [low, high] 之间
// 相当于我们在有序数组的“有效片段”中
if (node->val >= low && node->val <= high) {
ans += node->val;
// 只有当 node->val > low 时,左边才可能还有有效数字(还没触底)
if (node->val > low) dfs(node->left, low, high, ans);
// 只有当 node->val < high 时,右边才可能还有有效数字(还没触顶)
if (node->val < high) dfs(node->right, low, high, ans);
}
// 2. 如果当前节点 < low
// 说明我们在有序数组的左端,且当前位置太小了,左边的更小,肯定全废了
// 直接跳到右边去
else if (node->val < low) {
dfs(node->right, low, high, ans);
}
// 3. 如果当前节点 > high
// 说明我们在有序数组的右端,且当前位置太大了,右边的更大,肯定全废了
// 直接跳到左边去
else if (node->val > high) {
dfs(node->left, low, high, ans);
}
}
int rangeSumBST(TreeNode* root, int low, int high) {
int ans = 0;
dfs(root, low, high, ans);
return ans;
}
};
237

被折叠的 条评论
为什么被折叠?



