立聪堂德州十三局店 发表于 2024-11-12 21:16:40

力扣 653. 两数之和 IV 二叉树/binary-tree two-sum IV

数组系列

力扣数据结构之数组-00-概览
力扣.53 最大子数组和 maximum-subarray
力扣.128 最长连续序列 longest-consecutive-sequence
力扣.1 两数之和 N 种解法 two-sum
力扣.167 两数之和 II two-sum-ii
力扣.170 两数之和 III two-sum-iii
力扣.653 两数之和 IV two-sum-IV
力扣.015 三数之和 three-sum
力扣.016 最靠近的三数之和 three-sum-closest
力扣.259 较小的三数之和 three-sum-smaller
标题

给定一个二叉搜索树 root 和一个目的结果 k,如果二叉搜索树中存在两个元素且它们的和等于给定的目的结果,则返回 true。
示例 1:
      5
       / \
      3   6
   / \    \
    2   4    7输入: root = , k = 9
输出: true
示例 2:
      5
       / \
      3   6
   / \    \
    2   4    7输入: root = , k = 28
输出: false
提示:
二叉树的节点个数的范围是.
-10^4右子树
递归实现:
class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) {
      val = x;
    }
}

public class BinaryTree {
    public void preorderTraversal(TreeNode root) {
      if (root == null) {
            return;
      }
      System.out.print(root.val + " "); // 先访问根节点
      preorderTraversal(root.left);    // 遍历左子树
      preorderTraversal(root.right);   // 遍历右子树
    }
}3. 后序遍历 (Postorder Traversal)

遍历次序: 左子树 -> 右子树 -> 根节点
递归实现:
public class BinaryTree {
    public void inorderTraversal(TreeNode root) {
      if (root == null) {
            return;
      }
      inorderTraversal(root.left);   // 遍历左子树
      System.out.print(root.val + " "); // 访问根节点
      inorderTraversal(root.right);    // 遍历右子树
    }
}总结


[*]前序遍历:先访问根节点,再遍历左子树和右子树。
[*]中序遍历:先遍历左子树,再访问根节点,末了遍历右子树。
[*]后序遍历:先遍历左子树,再遍历右子树,末了访问根节点。
这些遍历方式的递归实现思绪基本相同,区别在于访问根节点的时机不同。在现实应用中,可以根据需求选择不同的遍历方式。
前中后是以 root 的节点为主视角,看什么时候被访问。
v1-前序遍历

思绪

我们可以把整个数组完全构建出来,然后复用从前的解法。
固然这样会比较慢,我们可以在遍历的时候找到对应的结果。
传递的值更新问题,我们用 resFlag 数组来记录末了的结果。
实现

public class BinaryTree {
    public void postorderTraversal(TreeNode root) {
      if (root == null) {
            return;
      }
      postorderTraversal(root.left);    // 遍历左子树
      postorderTraversal(root.right);   // 遍历右子树
      System.out.print(root.val + " "); // 访问根节点
    }
}结果

3ms 79.82
v2-中序遍历

思绪

采用中序遍历,其他保持不变。
代码

class Solution {
   public boolean findTarget(TreeNode root, int k) {
      // 构建结果列表
      Set<Integer> numSet = new HashSet<>();

      int[] resFlag = new int[]{1};
      resFlag = 0;
      preOrderTravel(numSet, root, k, resFlag);

      return resFlag != 0;
    }

    private void preOrderTravel(Set<Integer> numSet,
                              TreeNode root,
                              int k,
                              int[] resFlag) {
      if(root == null || resFlag != 0) {
            return;
      }

      // 符合
      int value = root.val;
      if(numSet.contains(k - value)) {
            resFlag = 1;
            return;
      }
      numSet.add(value);

      preOrderTravel(numSet, root.left, k, resFlag);
      preOrderTravel(numSet, root.right, k, resFlag);
    }
}结果

public boolean findTarget(TreeNode root, int k) {
    // 构建结果列表
    Set<Integer> numSet = new HashSet<>();
    int[] resFlag = new int[]{1};
    resFlag = 0;
    inOrderTravel(numSet, root, k, resFlag);
    return resFlag != 0;
}

private void inOrderTravel(Set<Integer> numSet,
                           TreeNode root,
                           int k,
                           int[] resFlag) {
    if(root == null || resFlag != 0) {
      return;
    }
    inOrderTravel(numSet, root.left, k, resFlag);
    // 符合
    int value = root.val;
    if(numSet.contains(k - value)) {
      resFlag = 1;
      return;
    }
    numSet.add(value);
    inOrderTravel(numSet, root.right, k, resFlag);
}v3-后序遍历

思绪

很简朴,调整为后续遍历即可。
实现

3ms 79.82%结果

4ms 29.82%
估计是服务器颠簸,也和测试用例有一定的关系。
v4-层序遍历

层序遍历

层序遍历(Level Order Traversal)是按层级次序从上到下、从左到右遍历二叉树。
与前序、中序、后序不同,层序遍历通常是使用广度优先搜索(BFS)实现的,常见的做法是使用队列来辅助遍历。
层序遍历的实现步骤:


[*]使用一个队列存储当前层的节点。
[*]先将根节点到场队列。
[*]然后逐层遍历队列,取出队首节点,访问该节点,并将它的左右子节点(如果有的话)依次到场队列。
[*]重复这个过程,直到队列为空。
层序遍历的 Java 实现:

    public boolean findTarget(TreeNode root, int k) {
      // 构建结果列表
      Set<Integer> numSet = new HashSet<>();

      int[] resFlag = new int[]{1};
      resFlag = 0;
      postOrderTravel(numSet, root, k, resFlag);

      return resFlag != 0;
    }

    private void postOrderTravel(Set<Integer> numSet,
                                 TreeNode root,
                                 int k,
                                 int[] resFlag) {
      if(root == null || resFlag != 0) {
            return;
      }

      postOrderTravel(numSet, root.left, k, resFlag);

      postOrderTravel(numSet, root.right, k, resFlag);

      // 符合
      int value = root.val;
      if(numSet.contains(k - value)) {
            resFlag = 1;
            return;
      }
      numSet.add(value);
    }代码说明:


[*]队列:我们使用 LinkedList 来实现队列,因为队列的特点是先入先出(FIFO)。
[*]访问节点:每次从队列中取出一个节点,访问它并将其左右子节点到场队列。
[*]层级遍历:这种方式会保证节点按照层次次序被访问,父节点先于子节点。
结合本题

// 层序遍历
public void levelOrderTraversal(TreeNode root) {
    if (root == null) {
      return;
    }
   
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root); // 将根节点加入队列
   
    while (!queue.isEmpty()) {
      TreeNode node = queue.poll(); // 取出队首元素
      System.out.print(node.val + " "); // 访问当前节点
      
      if (node.left != null) {
            queue.offer(node.left); // 左子节点加入队列
      }
      if (node.right != null) {
            queue.offer(node.right); // 右子节点加入队列
      }
    }
}结果

4ms 29.82
小结

层序遍历放在本题看起来没有特别大的优势。
不过层序遍历在有些场景照旧很有用的,比如 T337 打家劫舍 III。
v5-另有高手

思绪

除了这 4 种方式,另有其他更快的方式吗?
那就是我们其实对二叉树的理解照旧不够深入。
中序遍历之后,结果其实是一个升序数组。
也就是我们可以利用排序后的数组举行处理,结合 T167.
中序是:left>val>right
回顾 T167

其实就是两步
1)构建有序数组
2)双指针直接获取
固然双指针也可以用二分法,此处不再赘述、
java

    public boolean findTarget(TreeNode root, int k) {
      // 构建结果列表
      Set<Integer> numSet = new HashSet<>();

      // 队列 模拟
      int[] resFlag = new int[]{1};
      resFlag = 0;

      Queue<TreeNode> queue = new LinkedList<>();
      queue.offer(root);
      levelOrderTravel(numSet, queue, k, resFlag);

      return resFlag != 0;
    }

    private void levelOrderTravel(Set<Integer> numSet,
                                  Queue<TreeNode> queue,
                                  int k,
                                  int[] resFlag) {
      while (!queue.isEmpty()) {
            // 取出
            TreeNode root = queue.poll();
            // 符合
            int value = root.val;
            if(numSet.contains(k - value)) {
                resFlag = 1;
                return;
            }
            numSet.add(value);

            // 加入左右
            if(root.left != null) {
                queue.offer(root.left);
            }
            if(root.right != null) {
                queue.offer(root.right);
            }
      }
    }结果

public boolean findTarget(TreeNode root, int k) {
    // 构建结果列表
    Set<Integer> numSet = new HashSet<>();
    int[] resFlag = new int[]{1};
    resFlag = 0;
    inOrderTravel(numSet, root, k, resFlag);
    return resFlag != 0;
}

private void inOrderTravel(Set<Integer> numSet,
                           TreeNode root,
                           int k,
                           int[] resFlag) {
    if(root == null || resFlag != 0) {
      return;
    }
    inOrderTravel(numSet, root.left, k, resFlag);
    // 符合
    int value = root.val;
    if(numSet.contains(k - value)) {
      resFlag = 1;
      return;
    }
    numSet.add(value);
    inOrderTravel(numSet, root.right, k, resFlag);
}
小结

这种解法,其实已经很巧妙了。
本题的难度定位在简朴有点浪费,用到这种方式现实上已经结合了多个知识点。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 力扣 653. 两数之和 IV 二叉树/binary-tree two-sum IV