方法一:深度优先搜索+哈希表
使用深度优先搜索的方式遍历整棵树,用哈希表记录遍历过的节点的值
对于一个值为x的节点,检查哈希表中是否存在k-x即可。如果存在对应的元素,那么我们就可以在该树上找到两个节点的和为k;否则,将x放入到哈希表中
如果遍历完整棵树都不存在对应的元素,那么该树上不存在两个和为k的节点
import java.util.HashSet;
import java.util.Set;
class Solution {
Set<Integer> set = new HashSet<>();
public boolean findTarget(TreeNode root, int k) {
if (root == null) {
return false;
}
if (set.contains(k - root.val)) {
return true;
}
set.add(root.val);
return findTarget(root.left, k) || findTarget(root.right, k);
}
}
复杂度分析:
- 时间复杂度:O(n),其中n为二叉树的大小。需要遍历整棵树一次。
- 空间复杂度:O(n),其中n为二叉树的大小。主要为哈希表的开销,最坏情况下需要将每个节点加入哈希表一次。
方法二:广度优先搜索 + 哈希表
使用广度优先搜索的方式遍历整棵树,用哈希表记录遍历过的节点值
首先,创建一个哈希表和一个队列,将根节点加入队列中,然后执行以下步骤:(层序遍历)
- 从队列中取出队头,假设其值为x
- 检查哈希表中是否存在k-x,如果存在返回true
- 否则,将该节点的值加入哈希表,将该节点的左右非空节点加入队尾
- 重复以上步骤,直到队列为空
- 如果队列为空,说明树上不存在两个和为k的节点,返回false
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
class Solution {
public boolean findTarget(TreeNode root, int k) {
Set<Integer> set = new HashSet<>();
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (set.contains(k - node.val)) {
return true;
}
set.add(node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
return false;
}
}
复杂度分析:
- 时间复杂度:O(n),其中n为二叉搜索树的大小。需要遍历整棵树一次。
- 空间复杂度:O(n),其中n为二叉搜索树的大小。主要为哈希表和队列的开销,最坏情况下需要将每个节点加入哈希表和队列各一次。
方法三:深度优先搜索+中序遍历+双指针
二叉搜索树的中序遍历是升序排列的,可以将该二叉搜索树的中序遍历的结果记录下来,得到一个升序数组。
具体地,使用两个指针分别指向数组的头尾,当两个指针指向的元素之和小于k时,让左指针右移;当两个指针指向的元素之和大于k时,让右指针左移;当两个指针指向的元素之和等于k时,返回true
最终,当左指针和右指针重合时,树上不存在两个和为k的节点,返回false
import java.util.ArrayList;
import java.util.List;
class Solution {
List<Integer> list = new ArrayList<>();
public boolean findTarget(TreeNode root, int k) {
inorder(root);
int n = list.size();
int left = 0;
int right = n - 1;
while (left < right && right < n) {
int val = list.get(left) + list.get(right);
if (val < k) {
left++;
} else if (val > k) {
right--;
} else {
return true;
}
}
return false;
}
public void inorder(TreeNode root) {
if (root == null) {
return;
}
inorder(root.left);
list.add(root.val);
inorder(root.right);
}
}
复杂度分析:
- 时间复杂度:O(n),其中n为二叉搜索树的大小。我们需要遍历这棵树一次,并对得到的升序数组使用双指针遍历。
- 空间复杂度:O(n),其中n为二叉搜索树的大小。主要为升序数组的开销。
方法四:迭代 + 中序遍历 + 双指针
在方法三中,是在中序遍历得到的数组上进行双指针,这样需要消耗O(n)的空间,实际上可以将双指针的移动理解为在树上的遍历过程的一次移动。因为递归方法较难控制移动过程,因此可以使用迭代的方式进行遍历。
具体地,对于每一个指针新建一个栈。初始,让左指针移动到树的最左端点,并将路径保存在栈中,接下来依据栈来O(1)地计算出左指针的下一个位置。右指针也是同理。
计算下一个位置时,首先将位于栈顶的当前节点从栈中弹出,此时,首先判断当前节点是否存在右子节点,如果存在,那么将右子节点的最左子树加入到栈中;否则就完成了当前层的遍历,无需进一步修改栈的内容,直接回溯到上一层即可。
import java.util.Deque;
import java.util.LinkedList;
import java.util.Stack;
class Solution {
public boolean findTarget(TreeNode root, int k) {
TreeNode left = root;
TreeNode right = root;
Stack<TreeNode> leftStack = new Stack<>();
Stack<TreeNode> rightStack = new Stack<>();
leftStack.push(left);
while (left.left != null) {
leftStack.push(left.left);
left = left.left;
}
rightStack.push(right);
while (right.right != null) {
rightStack.push(right.right);
right = right.right;
}
while (left != right) {
int val = left.val + right.val;
if (val > k) {
right = getRight(rightStack);
} else if (val < k) {
left = getLeft(leftStack);
} else {
return true;
}
}
return false;
}
private TreeNode getLeft(Stack<TreeNode> leftStack) {
TreeNode root = leftStack.pop();
TreeNode node = root.right;
while (node != null) {
leftStack.push(node);
node = node.left;
}
return root;
}
private TreeNode getRight(Stack<TreeNode> rightStack) {
TreeNode root = rightStack.pop();
TreeNode node = root.left;
while (node != null) {
rightStack.push(node);
node = node.right;
}
return root;
}
}
复杂度分析:
- 时间复杂度:O(n),其中n为二叉搜索树的大小。在双指针的过程中,实际上遍历了整棵树一次。
- 空间复杂度:O(n),其中n为二叉搜索树的大小。主要为栈的开销,最坏情况下二叉搜索树为一条链,需要O(n)的栈空间。