• LeetCode力扣刷题——指针三剑客之二:树



    一、数据结构介绍

            作为(单)链表的升级版,我们通常接触的树都是二叉树(binary tree),即每个节点最多有 两个子节点;且除非题目说明,默认树中不存在循环结构。LeetCode 默认的树表示方法如下。

    1. struct TreeNode {
    2. int val;
    3. TreeNode *left;
    4. TreeNode *right;
    5. TreeNode(int x) : val(x), left(NULL), right(NULL) {}
    6. };

            可以看出,其与链表的主要差别就是多了一个子节点的指针。


    二、经典问题

    1. 树的递归

            对于一些简单的递归题,某些 LeetCode 达人喜欢写 one-line code,即用一行代码解决问题, 把 if-else 判断语句压缩成问号冒号的形式。我们也会展示一些这样的代码,但是对于新手,笔者仍然建议您使用 if-else 判断语句。

            在很多时候,树递归的写法与深度优先搜索的递归写法相同,因此本书不会区分二者。

    104. 二叉树的最大深度

    104. Maximum Depth of Binary Tree

            给定一个二叉树,找出其最大深度。

            二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

            说明: 叶子节点是指没有子节点的节点。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. int maxDepth(TreeNode* root) {
    15. return root? 1 + max(maxDepth(root->left), maxDepth(root->right)): 0;
    16. }
    17. };

    110. 平衡二叉树

    110. Balanced Binary Tree

            给定一个二叉树,判断它是否是高度平衡的二叉树。

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

            解法类似于求树的最大深度,但有两个不同的地方:一是我们需要先处理子树的深度再进行 比较,二是如果我们在处理子树时发现其已经不平衡了,则可以返回一个-1,使得所有其长辈节 点可以避免多余的判断(本题的判断比较简单,做差后取绝对值即可;但如果此处是一个开销较 大的比较过程,则避免重复判断可以节省大量的计算时间)。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. bool isBalanced(TreeNode* root) {
    15. return helper(root) != -1;
    16. }
    17. int helper(TreeNode* root){
    18. if(!root){
    19. return 0;
    20. }
    21. int left = helper(root->left), right = helper(root->right);
    22. if(left == -1 || right == -1 || abs(left - right) > 1){
    23. return -1;
    24. }
    25. return 1 + max(left, right);
    26. }
    27. };

    543. 二叉树的直径

    543. Diameter of Binary Tree

            给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

            注意:两结点之间的路径长度是以它们之间边的数目表示。

            同样的,我们可以利用递归来处理树。解题时要注意,在我们处理某个子树时,我们更新的 最长直径值和递归返回的值是不同的。这是因为待更新的最长直径值是经过该子树根节点的最长直径(即两侧长度);而函数返回值是以该子树根节点为端点的最长直径值(即一侧长度),使用这样的返回值才可以通过递归更新父节点的最长直径值)。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. // 主函数
    15. int diameterOfBinaryTree(TreeNode* root) {
    16. int diameter = 0;
    17. depth(root, diameter);
    18. return diameter;
    19. }
    20. // 辅函数 - 深度递归
    21. int depth(TreeNode* node, int& diameter){
    22. if(!node) return 0;
    23. int l = depth(node->left, diameter), r = depth(node->right, diameter);
    24. diameter = max(l + r, diameter); // 更新直径长度
    25. return 1 + max(l, r); // 返回深度
    26. }
    27. };

    437. 路径总和 III

    437. Path Sum III

            给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

            路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

            递归每个节点时,需要分情况考虑:(1)如果选取该节点加入路径,则之后必须继续加入连 续节点,或停止加入节点(2)如果不选取该节点加入路径,则对其左右节点进行重新进行考虑。 因此一个方便的方法是我们创建一个辅函数,专门用来计算连续加入节点的路径。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. // 主函数
    15. int pathSum(TreeNode* root, int targetSum) {
    16. if(!root) return 0;
    17. return pathSumStartWithRoot(root, targetSum) + pathSum(root->left, targetSum) + pathSum(root->right, targetSum);
    18. }
    19. // 辅函数 - 对当前节点连续加入节点,判断是否满足
    20. long long pathSumStartWithRoot(TreeNode* root, long long targetSum){
    21. if(!root) return 0;
    22. long long count = root->val == targetSum? 1: 0;
    23. count += pathSumStartWithRoot(root->left, targetSum - root->val);
    24. count += pathSumStartWithRoot(root->right, targetSum - root->val);
    25. return count;
    26. }
    27. };

    101. 对称二叉树

    101. Symmetric Tree

            给你一个二叉树的根节点 root , 检查它是否轴对称。

            判断一个树是否对称等价于判断左右子树是否对称。笔者一般习惯将判断两个子树是否相等 或对称类型的题的解法叫做“四步法”:(1)如果两个子树都为空指针,则它们相等或对称(2) 如果两个子树只有一个为空指针,则它们不相等或不对称(3)如果两个子树根节点的值不相等, 则它们不相等或不对称(4)根据相等或对称要求,进行递归处理。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. bool isSymmetric(TreeNode* root) {
    15. if(!root) return true;
    16. return helper(root->left, root->right);
    17. }
    18. bool helper(TreeNode* left, TreeNode* right){
    19. if(!left && !right) return true;
    20. if(!left || !right) return false;
    21. if(left->val != right->val) return false;
    22. return helper(left->left, right->right) && helper(left->right, right->left);
    23. }
    24. };

    1110. 删点成林

    1110. Delete Nodes And Return Forest

            给出二叉树的根节点 root,树上每个节点都有一个不同的值。

            如果节点值在 to_delete 中出现,我们就把该节点从树上删去,最后得到一个森林(一些不相交的树构成的集合)。

            返回森林中的每棵树。你可以按任意顺序组织答案。

            这道题最主要需要注意的细节是如果通过递归处理原树,以及需要在什么时候断开指针。同 时,为了便于寻找待删除节点,可以建立一个哈希表方便查找。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. vector delNodes(TreeNode* root, vector<int>& to_delete) {
    15. vectorforest;
    16. unordered_set<int> dict(to_delete.begin(), to_delete.end());
    17. root = helper(root, dict, forest);
    18. // 自下而上最顶点的节点是处理不到,所以递归完后需要对最顶点的节点做处理
    19. if(root){
    20. forest.push_back(root);
    21. }
    22. return forest;
    23. }
    24. TreeNode* helper(TreeNode* root, unordered_set<int> &dict, vector &forest){
    25. if(!root) return root;
    26. // 先进行递归操作,目的是自下而上对树进行操作,即树的后序遍历
    27. root->left = helper(root->left, dict, forest);
    28. root->right = helper(root->right, dict, forest);
    29. // 如果存在一个节点的val值存在于to_delete数组中
    30. if(dict.count(root->val)){
    31. // 把当前的左右子树压入forest数组中
    32. if(root->left){
    33. forest.push_back(root->left);
    34. }
    35. if(root->right){
    36. forest.push_back(root->right);
    37. }
    38. root = nullptr; // 删除当前节点
    39. }
    40. return root;
    41. }
    42. };

    2. 层次遍历

            我们可以使用广度优先搜索进行层次遍历。注意,不需要使用两个队列来分别存储当前层的 节点和下一层的节点,因为在开始遍历一层的节点时,当前队列中的节点数就是当前层的节点 数,只要控制遍历这么多节点数,就能保证这次遍历的都是当前层的节点。

    637. 二叉树的层平均值

    637. Average of Levels in Binary Tree

            给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10^-5 以内的答案可以被接受。

            利用广度优先搜索,我们可以很方便地求取每层的平均值。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. vector<double> averageOfLevels(TreeNode* root) {
    15. vector<double> ans;
    16. if(!root) return ans;
    17. queue q;
    18. q.push(root);
    19. while(!q.empty()){
    20. int count = q.size();
    21. double sum = 0;
    22. for(int i=0; i
    23. TreeNode* node = q.front();
    24. q.pop();
    25. sum += node->val;
    26. if(node->left){
    27. q.push(node->left);
    28. }
    29. if(node->right){
    30. q.push(node->right);
    31. }
    32. }
    33. ans.push_back(sum / count);
    34. }
    35. return ans;
    36. }
    37. };

    2. 前中后序遍历

            前序遍历、中序遍历和后序遍历是三种利用深度优先搜索遍历二叉树的方式。它们是在对节 点访问的顺序有一点不同,其它完全相同。考虑如下一棵树:

             前序遍历先遍历父结点,再遍历左结点,最后遍历右节点,我们得到的遍历顺序是 [1 2 4 5 3 6]。

    1. void preorder(TreeNode *root){
    2. visit(root);
    3. preorder(root->left);
    4. preorder(root->right);
    5. }

            中序遍历先遍历左节点,再遍历父结点,最后遍历右节点,我们得到的遍历顺序是 [4 2 5 1 3 6]。

    1. void inorder(TreeNode *root){
    2. inorder(root->left);
    3. visit(root);
    4. inorder(root->right);
    5. }

            后序遍历先遍历左节点,再遍历右结点,最后遍历父节点,我们得到的遍历顺序是 [4 5 2 6 3 1]。

    1. void postorder(TreeNode *root){
    2. postorder(root->left);
    3. postorder(root->right);
    4. visit(root);
    5. }

    105. 从前序与中序遍历序列构造二叉树

    105. Construct Binary Tree from Preorder and Inorder Traversal

            给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

    对于任意一颗树而言,前序遍历的形式总是:
            [ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]


    即根节点总是前序遍历中的第一个节点。而中序遍历的形式总是:
            [ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

             我们通过本题的样例讲解一下本题的思路。前序遍历的第一个节点是 4,意味着 4 是根节点。 我们在中序遍历结果里找到 4 这个节点,根据中序遍历的性质可以得出,4 在中序遍历数组位置 的左子数组为左子树,节点数为 1,对应的是前序排列数组里 4 之后的 1 个数字(9);4 在中序 遍历数组位置的右子数组为右子树,节点数为 3,对应的是前序排列数组里最后的 3 个数字。有了这些信息,我们就可以对左子树和右子树进行递归复原了。为了方便查找数字的位置,我们可以用哈希表预处理中序遍历的结果。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. unordered_map<int, int> index;
    14. public:
    15. TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    16. int n = preorder.size();
    17. // 构造哈希映射,帮助我们快速定位根节点
    18. for(int i=0; i
    19. index[inorder[i]] = i;
    20. }
    21. return buildTreeHelper(preorder, inorder, 0, n - 1, 0, n - 1);
    22. }
    23. TreeNode*buildTreeHelper(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right){
    24. if(preorder_left > preorder_right){
    25. return nullptr;
    26. }
    27. // 前序遍历中的第一个节点就是根节点
    28. int preorder_root = preorder_left;
    29. // 在中序遍历中定位根节点
    30. int inorder_root = index[preorder[preorder_root]];
    31. // 先把根节点建立出来
    32. TreeNode* root = new TreeNode(preorder[preorder_root]);
    33. // 得到左子树中的节点数目
    34. int size_left_subtree = inorder_root - inorder_left;
    35. // 递归地构造左子树,并连接到根节点
    36. // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
    37. root->left = buildTreeHelper(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
    38. // 递归地构造右子树,并连接到根节点
    39. // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
    40. root->right = buildTreeHelper(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
    41. return root;
    42. }
    43. };

    144. 二叉树的前序遍历

    144. Binary Tree Preorder Traversal

            给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

            因为递归的本质是栈调用,因此我们可以通过栈来实现前序遍历。注意入栈的顺序。

    递归写法:

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. vector<int> preorderTraversal(TreeNode* root) {
    15. vector<int> ans;
    16. helper(root, ans);
    17. return ans;
    18. }
    19. void helper(TreeNode* root, vector<int>& ans){
    20. if(!root){
    21. return;
    22. }
    23. ans.push_back(root->val);
    24. helper(root->left, ans);
    25. helper(root->right, ans);
    26. }
    27. };

    栈写法:

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. vector<int> preorderTraversal(TreeNode* root) {
    15. vector<int> ans;
    16. if(!root) return ans;
    17. stack s;
    18. s.push(root);
    19. while(!s.empty()){
    20. TreeNode* node = s.top();
    21. s.pop();
    22. ans.push_back(node->val);
    23. if(node->right){
    24. s.push(node->right);
    25. }
    26. if(node->left){
    27. s.push(node->left);
    28. }
    29. }
    30. return ans;
    31. }
    32. };

    3. 二叉查找树

            二叉查找树(Binary Search Tree, BST)是一种特殊的二叉树:对于每个父节点,其左子树中 所有节点的值小于等于父结点的值,其右子树中所有节点的值大于等于父结点的值。因此对于一 个二叉查找树,我们可以在 O(log n) 的时间内查找一个值是否存在:从根节点开始,若当前节点 的值大于查找值则向左下走,若当前节点的值小于查找值则向右下走。同时因为二叉查找树是有 序的,对其中序遍历的结果即为排好序的数组。

            一个二叉查找树的实现如下。

    1. template <class T>
    2. class BST{
    3. struct Node{
    4. T data;
    5. Node* left;
    6. Node* right;
    7. }
    8. Node* root;
    9. Node* makeEmpty(Node* t){
    10. if(t == NULL) return NULL;
    11. makeEmpty(t->left);
    12. makeEmpty(t->right);
    13. delete t;
    14. return NULL;
    15. }
    16. Node* insert(Node* t, T x){
    17. if(t == NULL){
    18. t = new Node;
    19. t->data = x;
    20. t->left = t->right = NULL;
    21. }else if(x < t->data){
    22. t->left = insert(t->left, x);
    23. }else if(x > t->data){
    24. t->right = insert(t->right, x);
    25. }
    26. return t;
    27. }
    28. Node* find(Node* t, T x){
    29. if(t == NULL) return NULL;
    30. if(x < t->data) return find(t->left, x);
    31. if(x > t->data) return find(t->right, x);
    32. return t;
    33. }
    34. Node* findMin(Node* t){
    35. if(t == NULL || t->left == NULL) return t;
    36. return findMin(t->left);
    37. }
    38. Node* findMax(Node* t){
    39. if(t == NULL || t->right == NULL) return t;
    40. return findMax(t->right);
    41. }
    42. Node* remove(Node* t ,T x){
    43. Node* temp;
    44. if(t == NULL){
    45. return NULL;
    46. }else if(x < t->data){
    47. t->left = remove(t->left, x);
    48. }else if(x > t->data){
    49. t->right = return(t->right, x);
    50. }else if(t->left && t->right){
    51. temp = findMin(t->right);
    52. t->data = temp->data;
    53. t->right = remove(t->right, t->data);
    54. }else{
    55. temp = t;
    56. if(t->left == NULL){
    57. t = t->right;
    58. }else if(t->right == NULL){
    59. t = t->left;
    60. }
    61. delete temp;
    62. }
    63. return t;
    64. }
    65. public:
    66. BST(): root(NULL){}
    67. ~BST(){
    68. root = makeEmpty(root);
    69. }
    70. void insert(T x){
    71. insert(root, x);
    72. }
    73. void remove(T x){
    74. remove(root, x);
    75. }
    76. };

    99. 恢复二叉搜索树

    99. Recover Binary Search Tree

            给你二叉搜索树的根节点 root ,该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下,恢复这棵树 

            我们可以使用中序遍历这个二叉查找树,同时设置一个 prev 指针,记录当前节点中序遍历 时的前节点。如果当前节点小于 prev 节点的值,说明需要调整次序。

            有一个技巧是如果遍历整个序列过程中只出现了一次次序错误,说明就是这两个相邻节点需要被交换;如果出现了两次次序错误,那就需要交换这两个节点。

    1. class Solution {
    2. public:
    3. void recoverTree(TreeNode* root) {
    4. TreeNode *mistake1 = nullptr, *mistake2 = nullptr, *prev = nullptr;
    5. inorder(root, mistake1, mistake2, prev);
    6. if(mistake1 && mistake2){
    7. int temp = mistake1->val;
    8. mistake1->val = mistake2->val;
    9. mistake2->val = temp;
    10. }
    11. }
    12. void inorder(TreeNode* root, TreeNode* &mistake1, TreeNode* &mistake2, TreeNode* &prev){
    13. if(!root) return;
    14. if(root->left){
    15. inorder(root->left, mistake1, mistake2, prev);
    16. }
    17. if(prev && root->val < prev->val){
    18. if(!mistake1){
    19. mistake1 = prev;
    20. mistake2 = root;
    21. }else{
    22. mistake2 = root;
    23. }
    24. }
    25. prev = root;
    26. if(root->right){
    27. inorder(root->right, mistake1, mistake2, prev);
    28. }
    29. }
    30. };

    669. 修剪二叉搜索树

    669. Trim a Binary Search Tree

            给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。

            所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。

            利用二叉查找树的大小关系,我们可以很容易地利用递归进行树的处理。

    1. /**
    2. * Definition for a binary tree node.
    3. * struct TreeNode {
    4. * int val;
    5. * TreeNode *left;
    6. * TreeNode *right;
    7. * TreeNode() : val(0), left(nullptr), right(nullptr) {}
    8. * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    9. * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
    10. * };
    11. */
    12. class Solution {
    13. public:
    14. TreeNode* trimBST(TreeNode* root, int low, int high) {
    15. if(!root){
    16. return root;
    17. }
    18. if(root->val > high){
    19. return trimBST(root->left, low, high);
    20. }
    21. if(root->val < low){
    22. return trimBST(root->right, low, high);
    23. }
    24. root->left = trimBST(root->left, low, high);
    25. root->right = trimBST(root->right, low, high);
    26. return root;
    27. }
    28. };

    4. 字典树

            字典树/前缀树(Trie)用于判断字符串是否存在或者是否具有某种字符串前缀。

    字典树,存储了单词 A、to、tea、ted、ten、i、in 和 inn,以及它们的频率

             为什么需要用字典树解决这类问题呢?假如我们有一个储存了近万个单词的字典,即使我们 使用哈希,在其中搜索一个单词的实际开销也是非常大的,且无法轻易支持搜索单词前缀。然而 由于一个英文单词的长度 n 通常在 10 以内,如果我们使用字典树,则可以在 O(n)——近似 O(1) 的时间内完成搜索,且额外开销非常小。

    208. 实现 Trie (前缀树)

    208. Implement Trie (Prefix Tree)

            Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

    请你实现 Trie 类:

            Trie() 初始化前缀树对象。
            void insert(String word) 向前缀树中插入字符串 word 。
            boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
            boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。

            以下是字典树的典型实现方法。

    1. class TrieNode{
    2. public:
    3. TrieNode* childNode[26];
    4. bool isVal;
    5. TrieNode(): isVal(false) {
    6. for(int i=0; i<26; ++i){
    7. childNode[i] = nullptr;
    8. }
    9. }
    10. };
    11. class Trie {
    12. TrieNode* root;
    13. public:
    14. Trie(): root(new TrieNode()) {
    15. }
    16. // 向字典树插入一个词
    17. void insert(string word) {
    18. TrieNode* temp = root;
    19. for(int i=0; isize(); ++i){
    20. if(!temp->childNode[word[i] - 'a']){
    21. temp->childNode[word[i] - 'a'] = new TrieNode();
    22. }
    23. temp = temp->childNode[word[i] - 'a'];
    24. }
    25. temp->isVal = true;
    26. }
    27. // 判断字典树里是否有一个词
    28. bool search(string word) {
    29. TrieNode* temp = root;
    30. for(int i=0; isize(); ++i){
    31. if(!temp){
    32. break;
    33. }
    34. temp = temp->childNode[word[i] - 'a'];
    35. }
    36. return temp? temp->isVal: false;
    37. }
    38. // 判断字典树是否有一个以词开始的前缀
    39. bool startsWith(string prefix) {
    40. TrieNode* temp = root;
    41. for(int i=0; isize(); ++i){
    42. if(!temp){
    43. break;
    44. }
    45. temp = temp->childNode[prefix[i] - 'a'];
    46. }
    47. return temp;
    48. }
    49. };
    50. /**
    51. * Your Trie object will be instantiated and called as such:
    52. * Trie* obj = new Trie();
    53. * obj->insert(word);
    54. * bool param_2 = obj->search(word);
    55. * bool param_3 = obj->startsWith(prefix);
    56. */

    三、巩固练习


    欢迎大家共同学习和纠正指教

  • 相关阅读:
    报表工具应该具备哪些特性?_光点科技
    敏捷项目管理术语大全
    计算机组成原理——存储器1-20
    springboot 定时任务
    【JavaScript 进阶教程】非 extends 的组合继承
    甩出11张图-让我们来构想(实现)一个倒排索引
    IEEE在指定期刊下搜索相关论文
    orElse,orElseGet,orElseThrow的使用
    三十七、【进阶】SQL的explain
    【js/es6】合集
  • 原文地址:https://blog.csdn.net/Hello_world_n/article/details/127836825