两数之和 IV - 输入二叉搜索树 图解题解
这道题到底在问什么
- 输入
- root=[50,30,80,20,40,null,90], k=140
- 输出
- true(50 加 90)
- 输入
- 同一棵树, k=200
- 输出
- false
先想最直接的笨办法
先看全局:目标 k 等于 140,手里准备一个空的哈希集合 vis,用来记「一路上见过的节点值」。我们从根节点 50 出发,按先序一个一个走,每到一个节点都做同一件事:查搭档、不在就存。(动画第 4 步)
最优解:一步一步想明白
- 3记住这句「先查 k 减 v 在不在集合里,不在就把 v 存进去」,下面每一帧都在套它。
- 4集合为空先看全局:目标 k 等于 140,手里准备一个空的哈希集合 vis,用来记「一路上见过的节点值」。我们从根节点 50 出发,按先序一个一个走,每到一个节点都做同一件事:查搭档、不在就存。
- 5两两枚举 O(n²)先想最直白的笨办法:把任意两个节点都配一遍,看哪一对加起来等于 140。这样要嵌套两层、共 n 乘 n 次,节点一多就慢。下面这套哈希集合法只走一遍,就能把它降到线性,注意看差别。
- 6当前值 50DFS 走到节点 50(紫色)。按套路,先别急着把它存进集合,要先问一句:谁和它搭档能凑成 140?那个搭档就是 140 减 50 等于 90。
- 790 不在去集合里查搭档 90:当前集合是 {(空)},翻一遍,里面没有 90。这一步只花常数时间,不用再去扫别的节点。
- 8集合 += 50既然没配上,就把 50 自己记进集合(节点变蓝表示已记下),留给后面还没走到的节点来配。集合现在是 {50},继续按先序往下走。
- 9当前值 30DFS 走到节点 30(紫色)。按套路,先别急着把它存进集合,要先问一句:谁和它搭档能凑成 140?那个搭档就是 140 减 30 等于 110。
- 10110 不在去集合里查搭档 110:当前集合是 {50},翻一遍,里面没有 110。这一步只花常数时间,不用再去扫别的节点。
- 11集合 += 30既然没配上,就把 30 自己记进集合(节点变蓝表示已记下),留给后面还没走到的节点来配。集合现在是 {50,30},继续按先序往下走。
- 12当前值 20DFS 走到节点 20(紫色)。按套路,先别急着把它存进集合,要先问一句:谁和它搭档能凑成 140?那个搭档就是 140 减 20 等于 120。
- 13120 不在去集合里查搭档 120:当前集合是 {50,30},翻一遍,里面没有 120。这一步只花常数时间,不用再去扫别的节点。
- 14集合 += 20既然没配上,就把 20 自己记进集合(节点变蓝表示已记下),留给后面还没走到的节点来配。集合现在是 {50,30,20},继续按先序往下走。
- 15当前值 40DFS 走到节点 40(紫色)。按套路,先别急着把它存进集合,要先问一句:谁和它搭档能凑成 140?那个搭档就是 140 减 40 等于 100。
- 16100 不在去集合里查搭档 100:当前集合是 {50,30,20},翻一遍,里面没有 100。这一步只花常数时间,不用再去扫别的节点。
- 17集合 += 40既然没配上,就把 40 自己记进集合(节点变蓝表示已记下),留给后面还没走到的节点来配。集合现在是 {50,30,20,40},继续按先序往下走。
- 18当前值 80DFS 走到节点 80(紫色)。按套路,先别急着把它存进集合,要先问一句:谁和它搭档能凑成 140?那个搭档就是 140 减 80 等于 60。
- 1960 不在去集合里查搭档 60:当前集合是 {50,30,20,40},翻一遍,里面没有 60。这一步只花常数时间,不用再去扫别的节点。
- 20集合 += 80既然没配上,就把 80 自己记进集合(节点变蓝表示已记下),留给后面还没走到的节点来配。集合现在是 {50,30,20,40,80},继续按先序往下走。
- 21当前值 90DFS 走到节点 90(紫色)。按套路,先别急着把它存进集合,要先问一句:谁和它搭档能凑成 140?那个搭档就是 140 减 90 等于 50。
- 2250 已见过去集合里查搭档 50:集合 {50,30,20,40,80} 里真的有 50!它是之前走过的节点留下来的,现在 50 加 90 正好等于 140,配对成功。
- 23答案 true把这一对点亮:50 和 90(两个绿色节点)加起来正好是目标 140。一找到就可以直接返回 true,剩下的节点都不用再看了。
- 2450 + 90 = 140回看全程:我们只把每个节点访问了一遍,靠集合一路记搭档。走到 90 时,发现它要找的 50 早被记下了,于是 50 加 90 等于 140,答案 true。整棵树没有任何两两嵌套的配对,全程一遍走完。
⚠️ 容易写错的地方
✗ 错:用嵌套遍历,对每个节点再扫一遍全树找补数
✓ 对:用哈希集合边走边查补数
嵌套是 O(n²),集合查补数是 O(1),一遍 O(n) 就够
✗ 错:同一个节点既当被查又当补数(自己配自己)
✓ 对:先查补数、再把自己存入
先查后存保证查到的补数一定是「别的节点」,不会拿自己凑数
✗ 错:以为必须用上 BST 有序的性质
✓ 对:本解把它当普通二叉树遍历即可
哈希集合法对任意二叉树都成立,不依赖有序;有序另有双指针解法
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def findTarget(self, root: Optional[TreeNode], k: int) -> bool:
def dfs(root):
if root is None:
return False
if k - root.val in vis:
return True
vis.add(root.val)
return dfs(root.left) or dfs(root.right)
vis = set()
return dfs(root)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool findTarget(TreeNode* root, int k) {
unordered_set<int> vis;
function<bool(TreeNode*)> dfs = [&](TreeNode* root) {
if (!root) {
return false;
}
if (vis.count(k - root->val)) {
return true;
}
vis.insert(root->val);
return dfs(root->left) || dfs(root->right);
};
return dfs(root);
}
};Java
import java.util.*;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
private Set<Integer> vis = new HashSet<>();
private int k;
public boolean findTarget(TreeNode root, int k) {
this.k = k;
return dfs(root);
}
private boolean dfs(TreeNode root) {
if (root == null) {
return false;
}
if (vis.contains(k - root.val)) {
return true;
}
vis.add(root.val);
return dfs(root.left) || dfs(root.right);
}
}复杂度
时间
O(n)
每个节点访问一次,集合查补数和插入都是均摊 O(1),n 是节点总数
空间
O(n)
哈希集合最多存 n 个值;递归栈最坏 O(n)(退化成链),平衡时 O(log n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 两数之和 IV - 输入二叉搜索树 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这道题是输入二叉搜索树,能不能利用「有序」这一点?+
可以。中序遍历 BST 会得到一个升序数组,再用「左右双指针」向中间夹:和偏小就左指针右移,偏大就右指针左移,O(n) 时间但需要 O(n) 额外数组存中序结果。哈希集合法不依赖有序、对任意二叉树都成立,两种都是 O(n) 时间,面试讲清取舍即可。
如果树非常大、不想占 O(n) 集合内存怎么办?+
可以用 BST 的双指针变体:维护两个迭代器,一个走中序正向(取最小)、一个走反向中序(取最大),向中间逼近,空间降到 O(h)(h 是树高)。实现稍复杂,但能把额外空间从 O(n) 压到树高级别。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 两数之和 IV - 输入二叉搜索树 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。