LeetCode刷题之二叉树(BST)

1,085 阅读18分钟

BST`

BST,即二叉查找树,对于任意一个节点node,node的左子树的都小于等于node,node的右子树都大于等于node

669. 修剪二叉搜索树(Easy)

给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。

示例 1:

输入: 
    1
   / \
  0   2

  L = 1
  R = 2

输出: 
    1
      \
       2

示例 2:

输入: 
    3
   / \
  0   4
   \
    2
   /
  1

  L = 1
  R = 3

输出: 
      3
     / 
   2   
  /
 1

题解:一开始想到的是用中序遍历二叉树,将符合的节点用数组存储起来,再将这棵二叉树复原,但是这样会破坏原有的结构,所以这种方法行不通。其实可以在遍历的过程中进行剪枝,一般二叉树的题目都可以用递归解决,这道题也不例外,先分析递归的3个重要条件

  • 返回值:已经剪枝之和的根节点
  • 调用单元做了什么:进行剪枝。
    • 首先判断如果root为空,那么直接返回空即可。
    • 然后就是要看根结点是否在范围内
      • 如果根结点值小于L,那么返回对其右子结点调用递归函数的值
      • 如果根结点大于R,那么返回对其左子结点调用递归函数的值。
    • 如果根结点在范围内,将其左子结点更新为对其左子结点调用递归函数的返回值
  • 终止条件:根节点为空
class Solution {
    public TreeNode trimBST(TreeNode root, int L, int R) {
        //base case
        if (root == null) {
            return null;
        }

        //如果根节点值大于R,返回对其左子节点调用递归函数的值
        if (root.val > R) {
            return trimBST(root.left, L, R);
        }
        //根节点值小于L,返回其对右子节点调用递归函数的值
        if (root.val < L) {
            return trimBST(root.right, L, R);
        }
        //如果在范围内,将其左节点更新为对其左子节点调用递归函数的值,右节点同理
        root.left = trimBST(root.left, L, R);
        root.right = trimBST(root.right, L, R);
        return root;
    }
}

230. 二叉搜索树中第K小的元素(Medium)

给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。

说明: 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

示例 1:

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 1

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
       5
      / \
     3   6
    / \
   2   4
  /
 1
输出: 3

进阶:

  • 如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化 kthSmallest 函数?

递归中序遍历解法:这道题让我们求第K个元素,那么可以使用中序遍历来查找,用变量i来记录遍历到第几个节点,当i=k时,返回当前节点的值即可。可以使用递归和非递归解答

class Solution {
    private int count = 0;
    private int val;
    
    public int kthSmallest(TreeNode root, int k) {
        inOrder(root, k);
        return val;
    }

    private void inOrder(TreeNode root, int k) {
        //base case
        if (root == null) {
            return;
        }
        
        //中序遍历
        //先左
        inOrder(root.left, k);
        //再根
        count++;
        if (count == k) {
            val = root.val;
            return;
        }
        //后右
        inOrder(root.right, k);
    }
}

非递归中序遍历解法

class Solution {
    public int kthSmallest(TreeNode root, int k) {
        //记录变量
        int count = 0;
        //准备一个栈来进行中序遍历
        Stack<TreeNode> stack = new Stack<>();
        TreeNode node = root;
        //遍历
        while (node != null || !stack.isEmpty()) {
            //节点不为空,一直将左节点压入栈
            while (node != null) {
                stack.push(node);
                node = node.left;
            }
            //节点为空,从栈中弹出一个节点
            node = stack.pop();
            count++;
            //如果count==k,返回node.val
            if (count == k) {
                return node.val;
            }
            //往右走
            node = node.right;
        }
        return 0;
    }
}

分治解法:这个方法有点类似于二分查找,首先计算出当前节点左子树的节点数量count。

  • 如果k<count+1,说明第k小节点在左子树上,对其左节点调用递归
  • 如果k>count+1,说明第k小节点在右子树上,对其右节点调用递归,此时k应该更新为k-count-1,因为已经减少了count+1个节点
  • 如果k=count+1,说明第k小节点就是当前节点,返回当前节点的值即可
class Solution {
    public int kthSmallest(TreeNode root, int k) {
        //统计左子树节点个数
        int leftCnt = count(root.left);
        //如果leftCnt == k - 1,说明第k小是当前节点,返回
        if (leftCnt == k - 1) {
            return root.val;
        }
        //如果k-1<leftCnt,说明第k小节点在左子树
        if (leftCnt > k - 1) {
            return kthSmallest(root.left, k);
        }
        return kthSmallest(root.right, k - leftCnt - 1);
    }

    private int count(TreeNode node) {
        //base case
        if (node == null) {
            return 0;
        }
        return 1 + count(node.left) + count(node.right);
    }
}

538. 把二叉搜索树转换为累加树(Easy)

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。

例如:

输入: 二叉搜索树:
              5
            /   \
           2     13

输出: 转换为累加树:
             18
            /   \
          20     13

递归解法:这道题让我把所有大于当前节点值加到当前节点上,根据BST的特点,所有大于当前节点的节点都在右子树上,那么我们可以使用改版的中序遍历:“右根左”,先求出右子树所有节点的和,加到当前节点上,再递归左子树

class Solution {
    public TreeNode convertBST(TreeNode root) {
        if (root != null) {
            dfs(root, 0);
        }
        return root;
    }

    private int dfs(TreeNode node, int sum) {
        if (node == null) {
            return sum;
        }
        //求出右子树所有大于当前节点的和
        sum = dfs(node.right, sum);
        //更新当前节点值
        node.val += sum;
        sum = dfs(node.left, node.val);
        return sum;
    }
}

同样,这道题还可以使用迭代解法

class Solution {
    public TreeNode convertBST(TreeNode root) {
        if (root == null) {
            return null;
        }
        
        int sum = 0;
        //准备一个栈
        Stack<TreeNode> stack = new Stack<>();
        TreeNode node = root;
        //“中序遍历”
        while (node != null || !stack.isEmpty()) {
            //将右节点全部压入栈
            while (node != null) {
                stack.push(node);
                node = node.right;
            }
            //为空的时候弹出
            node = stack.pop();
            //更新当前节点值
            node.val += sum;
            //更新sum
            sum = node.val;
            //往左走
            node = node.left;
        }
        return root;
    }
}

235. 二叉搜索树的最近公共祖先(Easy)

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉搜索树中。

递归解法:这道题让我们求出两节点的公共祖先,这是棵搜索二叉树,那么就比较好做了,因为搜索二叉树有个特点:左<根<右,两个给定节点肯定是一大一小,那么就可以做这样的判断:如果当前节点的值大于两个节点,那么说明p,q在左子树,往左子树走;反之,小于两个节点,往右子树走

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //如果走到最后,返回null
        if (root == null) {
            return null;
        }
        //大于两个节点,往左子树遍历
        if (root.val > p.val && root.val > q.val) {
            return lowestCommonAncestor(root.left, p, q);
        }
        //小于两个节点,往右子树遍历
        if (root.val < p.val && root.val < q.val) {
            return lowestCommonAncestor(root.right, p, q);
        }
        //返回当前节点
        return root;
    }
}

非递归解法:同样的,这道题也可以用非递归解答,思路一样

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while (root != null) {
            if (root.val > p.val && root.val > q.val) {
                root = root.left;
            }else if (root.val < p.val && root.val < q.val) {
                root = root.right;
            }else {
                break;
            }
        }
        return root;
    }
}

236. 二叉树的最近公共祖先(Medium)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:

  • 所有节点的值都是唯一的。
  • p、q 为不同节点且均存在于给定的二叉树中。

题解:这道题和上道题不同的是这道题不是搜索二叉树,所有就不能利用搜索二叉树的特点来进行解答。在在递归函数中,首先查看当前节点是否为空,若为空则返回空,若为p或者q中的其中一个,则返回当前节点,若都不等于,那就分情况讨论:

  1. 若p,q不在同一个子树,那么对左右子树调用递归函数之后,会分别返回p,q,那么当前节点就是最近公共祖先,返回即可
  2. 若p,q在同一棵子树,那么又有两种情况:
    • 返回p或者q中比较高位置的那个
    • 若p,q在同一层,那么返回两节点的公共祖先(第1种情况)

递归函数的3个条件:

  • 返回值:两个节点的最近公共祖先
  • 调用单元做了什么:判断当前节点是否为公共祖先,不是的话在左右子树里找(具体分析在上面)
  • 终止条件:当前节点为空
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //base case
        if (root == null) {
            return null;
        }
        //如果当前节点为p或者q其中一个,直接返回
        if (root == p || root == q) {
            return root;
        }
        //当前节点不是公共祖先,在左右子树中找公共祖先
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);
        //如果左右子树都找到了,说明当前节点是最近公共祖先
        if (left != null && right != null) {
            return root;
        }
        //如果其中一个为空,返回不为空的那个
        return left == null ? right : left;
    }
}

108. 将有序数组转换为二叉搜索树(Easy)

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定有序数组: [-10,-3,0,5,9],

一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5

题解:这道题让我将有序数组转换为二叉搜索树,二叉搜索树的特点是:左<根<右,根节点是中间值,那么数组中间值就是根节点,左右节点又分别是左右数组的中点,这道题实际上考察的是“二分查找”。

class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return helper(nums, 0, nums.length - 1);
    }

    private TreeNode helper(int[] nums, int left, int right) {
        //base case
        if (left > right) {
            return null;
        }
        int mid = left + (right - left) / 2;
        //根据当前数组的中间值构造出节点
        TreeNode cur = new TreeNode(nums[mid]);
        //左节点为左数组的中间值
        cur.left = helper(nums, left, mid - 1);
        cur.right = helper(nums, mid + 1, right);
        return cur;
    }
}

109. 有序链表转换二叉搜索树(Medium)

给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定的有序链表: [-10, -3, 0, 5, 9],

一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:

      0
     / \
   -3   9
   /   /
 -10  5

题解:这道题的核心解法和上道题一样,不同点在于这道题是用单链表存储值,而上道题用的是数组,数组可以使用index来访问数组中的元素。单链表没有index,不能通过“二分查找”很快找到中间点。可以使用“快慢指针”来找到中间点

class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        //用辅助函数来记录首尾
        return helper(head, null);
    }

    private TreeNode helper(ListNode head, ListNode tail) {
        //base case
        if (head == tail) {
            return null;
        }
        //使用快慢指针找到中点
        ListNode slow = head;
        ListNode fast = head;
        while (fast != tail && fast.next != tail) {
            slow = slow.next;
            fast = fast.next.next;
        }
        TreeNode cur = new TreeNode(slow.val);
        cur.left = helper(head, slow);
        cur.right = helper(slow.next, tail);
        return cur;
    }
}

653. 两数之和 IV - 输入 BST(Easy)

给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。

案例 1:

输入: 
    5
   / \
  3   6
 / \   \
2   4   7

Target = 9

输出: True

案例 2:

输入: 
    5
   / \
  3   6
 / \   \
2   4   7

Target = 28

输出: False

解法一:这道题是典型的TwoSum,可以使用HashSet来做。遍历二叉树就行,然后用一个HashSet,在递归函数函数中,如果node为空,返回false。如果k减去当前结点值在HashSet中存在,直接返回true;否则就将当前结点值加入HashSet,然后对左右子结点分别调用递归函数并且或起来返回即可

class Solution {
    public boolean findTarget(TreeNode root, int k) {
        HashSet<Integer> set = new HashSet<>();
        return helper(set, root, k);
    }

    private boolean helper(HashSet<Integer> set, TreeNode root, int k) {
        //base case
        if (root == null) {
            return false;
        }
        //判断当前元素加上set的元素能否等于k
        if (set.contains(k - root.val)) {
            return true;
        }
        //不等的话就添加进set
        set.add(root.val);
        
        //往左右子树遍历
        return helper(set, root.left, k) || helper(set, root.right, k);
    }
}

解法二:中序遍历二叉树,将节点存储到数组,然后用“双指针法”

class Solution {
    public boolean findTarget(TreeNode root, int k) {
        //中序遍历,存储到list
        ArrayList<Integer> list = new ArrayList<Integer>();
        inOrder(list, root);
        //双指针
        int i = 0;
        int j = list.size() - 1;
        while (i < j) {
            int sum = list.get(i) + list.get(j);
            if (sum == k) {
                return true;
            }else if (k < sum) {
                i++;
            }else {
                j--;
            }
        }
        return false;
    }

    private void inOrder(ArrayList<Integer> list, TreeNode root) {
        //base case
        if (root == null) {
            return;
        }
        //中序遍历
        inOrder(list, root.left);
        list.add(root.val);
        inOrder(list, root.right);
    }
}

解法三:也可以借助BST本身的性质,遍历二叉树的时候进行搜索

class Solution {
    private boolean search(TreeNode root, TreeNode cur, int y) {
        // binary seach on a BST(root), find node.val == y && node != cur
        while (root != null) {
            if (root.val == y && root != cur) {
                return true;
            } else if (y < root.val) {
                root = root.left;
            } else {
                root = root.right;
            }
        }
        return false;
    }
    
    public boolean findTarget(TreeNode root, int k) {
        if (root == null) return false;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode cur = stack.pop();
            boolean result = search(root, cur, k - cur.val); // cur.val is x, we are here to find y
            if (result) return true;
            if (cur.left != null) stack.push(cur.left);
            if (cur.right != null) stack.push(cur.right);
        }
        return false;
    }
}

530. 二叉搜索树的最小绝对差(Easy)

给定一个所有节点为非负值的二叉搜索树,求树中任意两节点的差的绝对值的最小值。

示例 :

输入:

   1
    \
     3
    /
   2

输出:
1

解释:
最小绝对差为1,其中 2 和 1 的差的绝对值为 1(或者 2 和 3)。

注意: 树中至少有2个节点。

题解:利用中序遍历BST有序的性质,比较相邻节点的差值即可,因为中序遍历BST是有序的

class Solution {
    private int minDiff = Integer.MAX_VALUE;
    //根节点没有前节点,所以设置为null
    private TreeNode preNode = null;
    
    public int getMinimumDifference(TreeNode root) {
        inOrder(root);
        return minDiff;
    }

    private void inOrder(TreeNode root) {
        //base case
        if (root == null) {
            return;
        }
        
        //中序遍历
        inOrder(root.left);
        //如果有前节点,更新minDiff
        if (preNode != null) {
            minDiff = Math.min(minDiff, Math.abs(preNode.val - root.val));
        }
        //更新前节点
        preNode = root;
        inOrder(root.right);
    }
}

501. 二叉搜索树中的众数(Easy)

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

假定 BST 有如下定义:

  • 结点左子树中所含结点的值小于等于当前结点的值
  • 结点右子树中所含结点的值大于等于当前结点的值
  • 左子树和右子树都是二叉搜索树 例如:
给定 BST [1,null,2,2],

   1
    \
     2
    /
   2
返回[2].

提示:如果众数超过1个,不需考虑输出顺序

进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)

解法一:答案可能不止一个,也就是有多个值出现的次数一样多,可以使用list记录,用preNode记录前一个节点,并统计当前节点出现的次数

class Solution {
    private int curCnt = 1;
    private int maxCnt = 1;
    private TreeNode preNode = null;
    
    public int[] findMode(TreeNode root) {
        List<Integer> maxCntNums = new ArrayList<>();
        inOrder(root, maxCntNums);
        int[] ret = new int[maxCntNums.size()];
        int idx = 0;
        for (int num : maxCntNums) {
            ret[idx++] = num;
        }
        return ret;
    }
    
    private void inOrder(TreeNode node, List<Integer> nums) {
        if (node == null) return;
        inOrder(node.left, nums);
        //如果有前节点,统计当前节点出现的次数
        if (preNode != null) {
            if (preNode.val == node.val) curCnt++;
            else curCnt = 1;
        }
        //如果出现次数大于maxCnt,更新,并清空list,重新添加
        if (curCnt > maxCnt) {
            maxCnt = curCnt;
            nums.clear();
            nums.add(node.val);
        } else if (curCnt == maxCnt) {
            //相等的话,就追加
            nums.add(node.val);
        }
        //更新前节点
        preNode = node;
        inOrder(node.right, nums);
    }
}

Trie

Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树。Trie树的基本性质可以归纳为:

(1)根节点不包含字符,除根节点之外每个节点只包含一个字符。

(2)从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。

(3)每个节点的所有子节点包含的字符串不相同。

208. 实现 Trie (前缀树)(Medium)

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

示例:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");   
trie.search("app");     // 返回 true

说明:

  • 你可以假设所有的输入都是由小写字母 a-z 构成的。
  • 保证所有输入均为非空字符串。

题解:比较简单的做法是每个节点都设置一个大小26的数组,用来表示26个字母

public class Trie {

        //前缀树的节点
        private class Node {
            //每个节点包含26个字母
            Node[] childs = new Node[26];
            //是否为叶子节点的标志
            boolean isLeaf;
        }

        //根节点为空节点
        private Node root = new Node();

        public Trie() {}

        public void insert(String word) {
            if (word == null) {
                return;
            }

            //从根节点出发,迭代添加
            Node pre = root;
            for (int i = 0; i < word.length(); i++) {
                int index = word.charAt(i) - 'a';
                //如果当前字符没有添加,就新建
                if (pre.childs[index] == null) {
                    pre.childs[index] = new Node();
                }
                //更新pre
                pre = pre.childs[index];
            }
            //添加完之后,将isLeaf置为true,标识存储完了
            pre.isLeaf = true;
        }

        public boolean search(String word) {
            if (word == null) {
                return false;
            }

            //从根节点开始查找
            Node pre = root;
            for (int i = 0; i < word.length(); i++) {
                int index = word.charAt(i) - 'a';
                //如果遇到一处没有,就返回false
                if (pre.childs[index] == null) {
                    return false;
                }
                //更新pre
                pre = pre.childs[index];
            }
            //返回最后一个字符的isLeaf
            return pre.isLeaf;
        }

        public boolean startsWith(String word) {
            if (word == null) {
                return false;
            }

            //从根节点开始查找
            Node pre = root;
            for (int i = 0; i < word.length(); i++) {
                int index = word.charAt(i) - 'a';
                //如果遇到一处没有,就返回false
                if (pre.childs[index] == null) {
                    return false;
                }
                //更新pre
                pre = pre.childs[index];
            }
            return true;
        }
    }

677. 键值映射(Medium)

实现一个 MapSum 类里的两个方法,insert 和 sum。

  • 对于方法 insert,你将得到一对(字符串,整数)的键值对。字符串表示键,整数表示值。如果键已经存在,那么原来的键值对将被替代成新的键值对。
  • 对于方法 sum,你将得到一个表示前缀的字符串,你需要返回所有以该前缀开头的键的值的总和。

示例 1:

输入: insert("apple", 3), 输出: Null
输入: sum("ap"), 输出: 3
输入: insert("app", 2), 输出: Null
输入: sum("ap"), 输出: 5

题解:这道题的难点在于sum方法,需要返回所有以该前缀开头的键的值的总和,很容易就想到使用上道题的前缀树来做。

class MapSum {

    /** Initialize your data structure here. */
    public MapSum() {

    }

    //自定义前缀树节点
    private class Node{
        //每个节点26个字符
        Node[] childs = new Node[26];
        boolean isLeaf;
        int val;
    }

    //根节点为空
    private Node root = new Node();

    public void insert(String key, int val) {
        insert(key, val, root);
    }

    private void insert(String key, int val, Node root) {
        if (key == null) {
            return;
        }

        //从根节点出发
        Node pre = root;
        for (int i = 0; i < key.length(); i++) {
            //判断当前字符是否已经存储过,存储过就跳过,否则新建
            int index = key.charAt(i) - 'a';
            
            //注意这里的空节点,不能采用Node tmp = pre.childs[index]的形式来判断,会出现空指针异常
            
            if (pre.childs[index] == null) {
                pre.childs[index] = new Node();
            }
            pre = pre.childs[index];
        }
        //添加完之后,标记置为true
        pre.isLeaf = true;
        pre.val = val;
    }

    public int sum(String prefix) {
        if (prefix == null) {
            return 0;
        }
        //遍历到prefix的最后一位字符所在的Node
        Node pre = root;
        for (int i = 0; i < prefix.length(); i++) {
            //找到当前字符所在的Node位置
            int index = prefix.charAt(i) - 'a';
            Node tmp = pre.childs[index];
            //如果不存在prefix这个前缀,返回0
            if (tmp == null) {
                return 0;
            }
            pre = tmp;
        }
        return sum(pre);
    }

    private int sum(Node node) {
        //base case
        if (node == null) {
            return 0;
        }
        //将当前节点值添加到sum
        int sum = node.val;
        //遍历当前节点的所有子节点
        for (Node child : node.childs) {
            //只进入不为空的子节点
            if (child != null) {
                sum += sum(child);
            }
        }
        return sum;
    }
}