代码随想录刷题总结:二叉树(具体题目)

226. 翻转二叉树

// recursion
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (nullptr != root) {
            TreeNode* left = root->left;
            root->left = invertTree(root->right);
            root->right = invertTree(left);
        }
        return root;
    }
};

589. 一般树的前序遍历

递归法与二叉树递归法类似,迭代法需要注意节点的入栈顺序是从右向左。

590. 一般树的后序遍历

递归法与二叉树递归法类似,迭代法需要注意节点的入栈顺序是从左向右,且最后对遍历结果进行反转。

100. 检查两个二叉树是否相同

// recursion
class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if (!p && !q)
            return true;
        if (!p || !q || p->val != q->val)
            return false;
        return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
};

101. 检查二叉树是否轴对称

// recursion
class Solution {
public:
    bool check(TreeNode *p, TreeNode *q) {
        if (!p && !q) return true;
        if (!p || !q) return false;
        return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
    }
    bool isSymmetric(TreeNode* root) {
        return check(root, root);
    }
};
// iteration - queue
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        queue<TreeNode*> que;
        que.push(root->left);
        que.push(root->right);
        
        while (!que.empty()) {
            TreeNode* leftNode = que.front(); que.pop();
            TreeNode* rightNode = que.front(); que.pop();
            if (!leftNode && !rightNode) {
                continue;
            }

            if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                return false;
            }
            que.push(leftNode->left);
            que.push(rightNode->right);
            que.push(leftNode->right);
            que.push(rightNode->left);
        }
        return true;
    }
};
// iteration - stack
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        stack<TreeNode*> st;
        st.push(root->right);
        st.push(root->left);
        while (!st.empty()) {
            TreeNode* leftNode = st.top(); st.pop();
            TreeNode* rightNode = st.top(); st.pop();
            if (!leftNode && !rightNode) {
                continue;
            }
            if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                return false;
            }
            st.push(rightNode->right);
            st.push(leftNode->left);
            st.push(rightNode->left);
            st.push(leftNode->right);
        }
        return true;
    }
};

572. 检查一棵树是否是另一棵树的子树(考虑空节点)

设父树为 s,子树为 t

  1. 先序遍历 s 上的节点,对每个 s 上的节点检查其与 t 是否相等,时间复杂度为 O ( ∣ s ∣ ∣ t ∣ ) O(|s||t|) O(s∣∣t),空间复杂度为 O ( m a x { d s , d t } ) O(max\{d_s,d_t\}) O(max{ds,dt}),即 s 的深度和 t 的深度的较大者

  2. 获取 s 和 t 的前序遍历序列,需要考虑空节点以保证序列可以唯一表示一棵二叉树,将左空节点记为 lNull,将右空节点记为 rNull。使用 KMP 算法进行串匹配即可,时间复杂度为 O ( ∣ s ∣ + ∣ t ∣ ) O(|s|+|t|) O(s+t),空间复杂度为 O ( ∣ s ∣ + ∣ t ∣ ) O(|s|+|t|) O(s+t)

    // -10000 <= TreeNode::val <= 10000
    class Solution {
        static constexpr int lNull = INT_MIN;
        static constexpr int rNull = INT_MAX;
    
        using Array = array<int, 2048ull>;
        Array sArr, tArr;
        int sArrLen = 0, tArrLen = 0;
    
        static void traverse(TreeNode* root, Array& arr, int& len) {
            if (!root)
                return;
            arr[len++] = root->val;
            if (root->left)
                traverse(root->left, arr, len);
            else
                arr[len++] = lNull;
            if (root->right)
                traverse(root->right, arr, len);
            else
                arr[len++] = rNull;
        }
    
    public:
        bool isSubtree(TreeNode* s, TreeNode* t) {
            // get the two pre-order arrays
            traverse(s, this->sArr, this->sArrLen);
            traverse(t, this->tArr, this->tArrLen);
    
            /* --------- KMP Algorithm ---------*/
    
            vector<int> next(tArrLen, -1);
    
            // solve the "next" array
            int prefixIdx = -1;
            for (int suffixIdx = 1; suffixIdx < tArrLen; ++suffixIdx) {
                while (prefixIdx >= 0 && tArr[suffixIdx] != tArr[prefixIdx + 1]) {
                    prefixIdx = next[prefixIdx];
                }
                if (tArr[suffixIdx] == tArr[prefixIdx + 1]) {
                    next[suffixIdx] = prefixIdx + 1;
                }
            }
    
            // match
            int tIdx = -1;
            for (int sIdx = 0; sIdx < sArrLen; ++sIdx) {
                while (tIdx >= 0 && sArr[sIdx] != tArr[tIdx + 1]) {
                    tIdx = next[tIdx];
                }
                if (sArr[sIdx] == tArr[tIdx + 1]) {
                    ++tIdx;
                }
                if (tIdx == tArrLen - 1) {
                    return true;
                }
            }
            return false;
        }
    };
    
  3. 树哈希法。设计一个二叉树的哈希函数,对 s 中每一个子树计算其哈希值,若某个子树的哈希值与 t 的哈希值相同,则命题成立(假定不考虑哈希碰撞)。一种哈希方法是令 f ( t ) = t . v a l + 37 p ( ∣ t . l e f t ∣ ) ⋅ f ( t . l e f t ) + 179 p ( ∣ t . r i g h t ∣ ) ⋅ f ( t . r i g h t ) f(t)=t.val+37p(|t.left|)\cdot f(t.left)+179p(|t.right|)\cdot f(t.right) f(t)=t.val+37p(t.left)f(t.left)+179p(t.right)f(t.right),其中函数 p ( n ) p(n) p(n) 表示第 n n n 个质数,37 和 179 可以换成其他质数。时间复杂度近似为 O ( m a x { ∣ s ∣ , ∣ t ∣ } ) O(max\{|s|,|t|\}) O(max{s,t}),空间复杂度近似为 O ( m a x { ∣ s ∣ , ∣ t ∣ } ) O(max\{|s|,|t|\}) O(max{s,t})

    质数表的获取方法 1:埃氏筛法。对 2 到 N 范围内的正整数,设初始质数为 p(p 初始为 2),每次循环排除 p 的所有倍数,即 2p、3p、4p … kp <= N,排除完之后令 p 为下一个质数,即未排除的数中的 p 的下一个数。循环结束条件为 p 后面没有数。最后剩下的数即为 2 到 N 范围内的所有质数。
    质数表的获取方法 2:欧拉筛法(线性筛)。埃氏筛法的问题在于同一个合数会被检查多次。欧拉筛法与埃氏筛法的区别是会把获取的质数存储在一个列表中。每次循环不再排除所有的倍数,而是排除当前质数表对应的倍数。假定当前质数为 13,则当前循环会排除 2 * 13、3 * 13、5 * 13、7 * 13、11 * 13、13 * 13 这些数。

    class Solution {
    public:
        static constexpr int MAX_N = 1005;
        static constexpr int MOD = int(1E9) + 7;
    
        bool vis[MAX_N]{false}; // vis[i]: i has been visited
        int p[MAX_N]{0}; // p[i]: ith prime number, p[0] == 2
        int tot = 0; // length of p
    
        // Euler Algorithm
        void getPrime() {
            vis[0] = vis[1] = true; tot = 0;
            for (int i = 2; i < MAX_N; ++i) {
                if (!vis[i])
                    p[++tot] = i;
                for (int j = 1; j <= tot && i * p[j] < MAX_N; ++j) {
                    vis[i * p[j]] = true;
                    if (i % p[j] == 0) break;
                }
            }
        }
    
        // status of a subtree
        struct Status {
            int f; // hash value 
            int s; // node number
            Status(int f_ = 0, int s_ = 0) 
                : f(f_), s(s_) {}
        };
    
        // hash map whose
        // key: subtree, value: subtree's status
        unordered_map <TreeNode *, Status> hS, hT;
        
        // traverse each node by DFS order and calculate each subtree's status
        // the results will be stored at hS and hT.
        void dfs(TreeNode *o, unordered_map <TreeNode *, Status> &h) {
            h[o] = Status(o->val, 1);
            if (!o->left && !o->right)
                return;
            if (o->left) {
                dfs(o->left, h);
                h[o].s += h[o->left].s;
                h[o].f = (h[o].f + (31LL * h[o->left].f * p[h[o->left].s]) % MOD) % MOD;
            }
            if (o->right) {
                dfs(o->right, h);
                h[o].s += h[o->right].s;
                h[o].f = (h[o].f + (179LL * h[o->right].f * p[h[o->right].s]) % MOD) % MOD;
            }
        }
    
        bool isSubtree(TreeNode* s, TreeNode* t) {
            getPrime();
    
            dfs(s, hS);
            dfs(t, hT);
    
            int tHash = hT[t].f;
            for (const auto &[k, v]: hS) {
                if (v.f == tHash) {
                    return true;
                }
            }
            return false;
        }
    };
    

104. 求二叉树的最大深度

递归求解即可。或使用层序遍历记录层数。

559. 一般树的最大深度

与上题类似。

111. 求二叉树的最小深度

递归求解时可以剪枝,若当前处理节点的深度已经大于维护的最小深度则不再处理其子树。若使用层序遍历,则只要检测到叶子节点就直接返回深度,如此即为最小深度。

// recursion
class Solution {
    int result = INT_MAX;
    void traverse(TreeNode* root, int depth) {
        if (depth >= result)
            return;
        if (!root->left && !root->right) {
            result = min(result, depth);
            return;
        }
        if (root->left) {
            traverse(root->left, depth + 1);
        }
        if (root->right) {
            traverse(root->right, depth + 1);
        }
    }
public:
    int minDepth(TreeNode* root) {
        if (!root)
            return 0;
        traverse(root, 1);
        return result;
    }
};

222. 求完全二叉树的节点个数

  1. 直接使用一般二叉树的节点计数方法。

    class Solution {
    public:
        int countNodes(TreeNode* root) {
            if (nullptr == root) return 0;
            return 1 + countNodes(root->left) + countNodes(root->right);
        }
    };
    
  2. 在 1 的基础上,每次递归先检查当前子树是否是满二叉树(最左节点和最右节点深度相同),若是满二叉树,则使用公式 2 h − 1 2^h-1 2h1 计算该子树的节点个数,否则使用传统的递归计算方法。时间复杂度 O ( l o g 2 n ) O(log^2n) O(log2n)

    class Solution {
    public:
        int countNodes(TreeNode* root) {
            if (root == nullptr) return 0;
            TreeNode* left = root->left;
            TreeNode* right = root->right;
            int leftHeight = 0, rightHeight = 0;
            while (left) {  // 求左子树深度
                left = left->left;
                leftHeight++;
            }
            while (right) { // 求右子树深度
               right = right->right;
                rightHeight++;
            }
            if (leftHeight == rightHeight)
                return (2 << leftHeight) - 1; // 注意(2 << 1) 相当于2 ^ 2,所以 leftHeight 初始为 0
            return countNodes(root->left) + countNodes(root->right) + 1;
        }
    };
    

110. 检查二叉树是否是平衡二叉树

递归计算每棵子树的高度,顺带检查每棵子树是否平衡即可,注意检测到非平衡树后要剪枝。

257. 获得二叉树从根到所有叶子的路径

DFS 回溯记录路径即可。

404. 求二叉树所有左叶子之和

递归求和,注意判断左孩子是否是叶子节点即可。

513. 求二叉树最深层的最左边的值

  1. 维护一个当前到达过的最深深度和对应的最左边的值,使用递归先序遍历,当且仅当(1. 当前深度超过维护的最深深度 2. 当前节点为叶子节点)时才更新最左边的值为当前节点值,直到递归结束。
  2. 使用分层记录结果的层序遍历,返回最后一行的第一个值即可。

112. 检查是否有根到叶子的路径中的节点的值的和等于给定值

直接先序遍历,每次将给定值减掉当前节点的值,若找到某叶子节点的值恰好为给定值则条件成立。

113. 返回所有路径和等于给定值的根到叶子的路径

  1. 仿照 112 题进行先序遍历,不同的是要记录当前路径,且要对给定值和当前路径进行回溯处理。
  2. 使用层序遍历,使用哈希表记录每个节点的父节点,队列中记录节点的同时还必须记录当前节点对应的给定值,当前节点为叶子节点时通过哈希表写入结果。

105. 从中序和先序遍历序列还原二叉树

递归分块求解

struct TreeRange {
    int pb, pe, ib, ie;
};

class Solution {
public:
    TreeNode* build(const vector<int>& preorder, const vector<int>& inorder, const TreeRange tr) {
        const int rootVal = preorder[tr.pb];
        TreeNode* root = new TreeNode(rootVal);

        // current tree has just one leaf node
        if (1 == tr.pe - tr.pb)
            return root;

        // root's index at inorder
        int im = tr.ib;
        for (; im < tr.ie; ++im) {
            if (rootVal == inorder[im]) break;
        }

        TreeRange lr, rr;
        int lsz = im - tr.ib, rsz = tr.ie - im - 1;

        // build left subtree
        if (0 != lsz) {
            lr.ib = tr.ib;
            lr.ie = im;
            lr.pb = tr.pb + 1;
            lr.pe = lr.pb + lsz;
            root->left = build(preorder, inorder, lr);
        }

        // build right subtree
        if (0 != rsz) {
            rr.ib = im + 1;
            rr.ie = tr.ie;
            rr.pb = tr.pb + 1 + lsz;
            rr.pe = tr.pe;
            root->right = build(preorder, inorder, rr);
        }

        return root;
    }

    TreeNode* buildTree(const vector<int>& preorder, const vector<int>& inorder) {
        int n = inorder.size();
        return build(preorder, inorder, { 0, n, 0, n });
    }
};

106. 从中序和后序遍历序列还原二叉树

递归分块求解

class Solution {
private:
    // 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd)
    TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) {
        if (postorderBegin == postorderEnd) return NULL;

        int rootValue = postorder[postorderEnd - 1];
        TreeNode* root = new TreeNode(rootValue);

        if (postorderEnd - postorderBegin == 1) return root;

        int delimiterIndex;
        for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) {
            if (inorder[delimiterIndex] == rootValue) break;
        }
        // 切割中序数组
        // 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
        int leftInorderBegin = inorderBegin;
        int leftInorderEnd = delimiterIndex;
        // 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
        int rightInorderBegin = delimiterIndex + 1;
        int rightInorderEnd = inorderEnd;

        // 切割后序数组
        // 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)
        int leftPostorderBegin =  postorderBegin;
        int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin; // 终止位置是 需要加上 中序区间的大小size
        // 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)
        int rightPostorderBegin = postorderBegin + (delimiterIndex - inorderBegin);
        int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了

        root->left = traversal(inorder, leftInorderBegin, leftInorderEnd,  postorder, leftPostorderBegin, leftPostorderEnd);
        root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd);

        return root;
    }

public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return NULL;
        // 左闭右开的原则
        return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
    }
};    

654. 构造“最大二叉树”

给定一个不重复的整数数组 nums。“最大二叉树”可以用下面的算法从 nums 递归地构建:

  1. 创建一个根节点,其值为 nums 中的最大值。
  2. 递归地在最大值 左边 的 子数组前缀上 构建左子树。
  3. 递归地在最大值 右边 的 子数组后缀上 构建右子树。

直接模拟递归定义即可。

700. 搜索二叉搜索树

见基础知识部分。

98. 验证二叉树是否是二叉搜索树

  1. 根据二叉搜索树的定义递归判断。
  2. 检查中序遍历序列是否是严格升序。可以不构造中序遍历序列,只需按照中序顺序遍历二叉树并比较当前节点和前序节点之间的大小关系即可。

530. 求二叉搜索树的最小绝对差

中序遍历二叉树并求当前节点和前序节点之间的绝对差即可。

501. 求允许相同值的二叉搜索树的众数

中序遍历二叉树并根据当前节点和前序节点之间的的关系确定众数即可。

236. 求二叉树中的 p、q 两节点的最近公共祖先

将函数意义改为如下含义:

  1. 若树中存在 p 和 q,则返回最近公共祖先。
  2. 若只存在 p 和 q 的其中一个,则返回 root。
  3. 若不包含 p 或 q,则返回 NULL。

若根节点 root 为 p、q 或空,则显然结果是 root。否则,递归处理左子树和右子树,得到对应的返回值 left 和 right。若 left 和 right 均非空,则说明 p 和 q 分别在两个子树中,返回 root。若二者均为空,则说明 root 对应的子树既不包含 p 也不包含 q。若其中一个为空另一个为非空,则 p 或 q 在非空的那一侧,返回非空的那一侧。如此,若遇到返回非空 root 的场合,则可以说明 root 的兄弟节点必不含 p 或 q,root 的父节点对应的递归必定返回 root,其祖父节点同理。

由于题目给出的用例一定是同时包含 p 和 q 的树,所以该含义能返回正确结果。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == q || root == p || root == NULL) return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if (left != NULL && right != NULL) return root;
        if (left == NULL) return right;
        return left;
    }
};

235. 求二叉搜索树中 p、q 两节点的最近公共祖先

若当前节点值恰好介于 p、q 之间,则返回该节点,否则看情况将当前节点设为左孩子或右孩子,进行下一步递归或迭代。

701. 向二叉搜索树中的插入节点

见基础知识部分。

450. 删除二叉搜索树中的节点

  1. 见基础知识部分。
  2. 要删除节点有两个非空子树时,也可以把要删除节点的左子树接到右子树的最小节点的左边,这样要删除的节点就变成了只有一个非空子树的节点。

669. 删除二叉树中所有值在 [L, R) 区间外的节点

若根节点值在区间左边,则删除根节点和左子树,返回递归修剪过的右子树。若根节点值在区间右边,则删除根节点和右子树,返回递归修剪过的左子树。
若根节点值在区间内部,则递归修剪两个子树。

class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if (root == nullptr) return nullptr;
        if (root->val < low) return trimBST(root->right, low, high);
        if (root->val > high) return trimBST(root->left, low, high);
        root->left = trimBST(root->left, low, high);
        root->right = trimBST(root->right, low, high);
        return root;
    }
};

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

二分递归构造即可。

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

反向中序遍历并同时更新节点值即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值