LeetCode 二叉树问题
@
目录
构建二叉查找树
对于每个父节点,其左子树中所有节点的值小于等于父结点的值,其右子树中所有节点的值大于等于父结点的值,因此对于一个二叉查找树,我们可以在$O(nlogn)$ 的时间内查找一个值是否存在:从根节点开始,若查找值小于当前节点的值则向左下走,若查找值大于当前节点的值则向右下走。
二叉查找树的中序遍历结果即为排序好的数组
- 二叉查找树的实现:
- 清空操作:
- 当前节点为空:返回空指针
- 当前节点不为空:递归清空左、右子树,删除当前节点,返回空指针
- 插入操作:
- 当前节点为空:直接插入元素,并令其左、右子节点为空
- 当前节点不为空:
- 插入值小于当前节点的值:递归插入左子树
- 插入值大于当前节点的值:递归插入右子树
- 返回当前节点
- 查找操作:
- 当前节点为空: 返回空指针
- 当前节点不为空:
- 查找值小于当前节点的值:返回递归查找左子树的结果
- 查找值大于当前节点的值:返回递归查找右子树的结果
- 查找值等于当前节点的值:返回当前节点
- 删除操作:
- 当前节点为空:返回空指针
- 当前节点不为空:
- 删除值小于当前节点的值:令左子树等于递归删除左子树的结果
- 删除值大于当前节点的值:令右子树等于递归删除右子树的结果
- 删除值等于当前节点的值:
- 左、右子节点不为空:找到右子树的最小值,令当前节点的值等于该最小值,令右子树等于递归删除右子树最小值节点的结果
- 左、右子节点至少一个为空:左子树为空,令当前节点等于右子树;右子树为空,令当前节点等于左子树
- 清空操作:
template <class T> class BST { struct Node { T data; Node* left; Node* right; }; Node* root; Node* makeEmpty(Node* t) { // 清空操作 if (t == NULL) return NULL; makeEmpty(t->left); makeEmpty(t->right); delete t; return NULL; } Node* insert(Node* t, T x) { // 插入操作 if (t == NULL) { t = new Node; t->data = x; t->left = t->right = NULL; } else if (x < t->data) { t->left = insert(t->left, x); } else if (x > t->data) { t->right = insert(t->right, x); } return t; } Node* find(Node* t, T x) { // 查找操作 if (t == NULL) return NULL; if (x < t->data) return find(t->left, x); if (x > t->data) return find(t->right, x); return t; } Node* findMin(Node* t) { if (t == NULL || t->left == NULL) return t; return findMin(t->left); } Node* findMax(Node* t) { if (t == NULL || t->right == NULL) return t; return findMax(t->right); } Node* remove(Node* t, T x) { // 删除操作 Node* temp; if (t == NULL) return NULL; else if (x < t->data) t->left = remove(t->left, x); else if (x > t->data) t->right = remove(t->right, x); else if (t->left && t->right) { temp = findMin(t->right); t->data = temp->data; t->right = remove(t->right, t->data); } else { temp = t; if (t->left == NULL) t = t->right; else if (t->right == NULL) t = t->left; delete temp; } return t; } public: BST() : root(NULL) {} ~BST() { root = makeEmpty(root); } void insert(T x) { insert(root, x); } void remove(T x) { remove(root, x); } };
1. 遍历问题
105. 从前序与中序遍历序列构造二叉树
给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。
- 思路分析:
- preorder 的第一个数一定是整个二叉树的根节点
- preorder 的第一个数在 inorder 中的索引可以将 inorder 分为左右两部分,分别为左右子树
- 左右子树继续根据性质递归划分
- 具体实现:
- 建立哈希表存储 inorder 中节点值与其索引的映射
- 假设根节点的在 inorder 的索引为 index
- 左子树的节点数为 leftLen = (index - 1) - s0 + 1
- 左子树的根节点在前序遍历数组的索引为 s1 + 1
- 右子树的根节点在前序遍历数组的索引为 s1 + leftLen + 1
- 参数说明:
- s0 树在中序遍历的起始索引
- e0 树在中序遍历的终止索引
- s1 树的根节点在前序遍历数组的索引
- s0、e0为子树在中序遍历结果中的起止索引,s1为子树根节点在前序遍历中的索引,前序遍历只起到提供子树根节点值的作用
class Solution { public: TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { if (preorder.empty()) { return nullptr; } unordered_map<int, int> hash; // 建立中序遍历结果中节点值与索引的哈希映射 for (int i = 0; i < preorder.size(); i++) { hash[inorder[i]] = i; } return helper(hash, preorder, 0, preorder.size() - 1, 0); } TreeNode* helper(unordered_map<int, int>& hash, vector<int>& preorder, int s0, int e0, int s1) { if (s0 > e0) { return nullptr; } int mid = preorder[s1], index = hash[mid], leftLen = index - 1 - s0 + 1; TreeNode *node = new TreeNode(mid); node->left = helper(hash, preorder, s0, index - 1, s1 + 1); // 构建左子树 node->right = helper(hash, preorder, index + 1, e0, s1 + leftLen + 1); // 构建右子树 return node; } };
- 简洁实现法
- 通过构建类的变量存储哈希映射
- 根据前序遍历结果确定根节点的索引 root
- 根据根节点索引 root 以及中序遍历的哈希映射,确定树的左、右边界 left right
class Solution { public: TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { this->preorder = preorder; for (int i = 0; i < inorder.size(); i++) { hash[inorder[i]] = i; } return helper(0, 0, inorder.size() - 1); } private: vector<int> preorder; unordered_map<int, int> hash; TreeNode* helper(int root, int left, int right) { if (left > right) { return nullptr; } int rootval = preorder[root]; int m = hash[rootval]; // 根节点在中序遍历的索引 TreeNode* node = new TreeNode(rootval); node->left = helper(root + 1, left, m - 1); // 左子树的左边界一直为 left = 0 node->right = helper(root + m - left + 1, m + 1, right); // 右子树的右边界一直味 right = size() - 1 return node; } };
124. 二叉树中的最大路径和
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中至多出现一次 。该路径至少包含一个节点,且不一定经过根节点。路径和 是路径中各节点值的总和。Link
- 递归求解
- 当前节点的最大值为 val + leftGain + rightGain
- 当前节点向上一节点的返回值为 val + max(leftGain,rightGain)
- 因为同一节点不能经过两次,因此必须选择当前节点左、右子树中较大的一个进行返回
- 例如,节点 11 的最大值为 20,但其向上返回的最大值为 7 + 11 = 18,因此一个节点只能出现一次,而要同时访问 7 2 必须经过 11 两次
class Solution { private: int maxSum = INT_MIN; public: int maxPathSum(TreeNode* root) { maxGain(root); return maxSum; } int maxGain(TreeNode* root) { if (!root) return 0; int leftGain = max(maxGain(root->left), 0); int rightGain = max(maxGain(root->right), 0); int innerMax = root->val + leftGain + rightGain; // 以当前节点为根节点的最大值 maxSum = max(maxSum, innerMax); return root->val + max(leftGain, rightGain); // 经过当前节点的最大增益 } };
2. 二叉查找树
99. 恢复二叉搜索树
给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
- 递归思想:
- 终止条件:节点为空
- 递归操作:
- 中序遍历:设一个指针指向中序遍历的前一个节点,与当前节点的值做对比,如果小于前一个节点,说明排序错误
- 技巧: 整个遍历过程只出现一次排序错误,交换两个相邻节点;出现两次排序错误,交换这两个节点
- 节点为空时不进行操作直接返回上一级递归
class Solution { public: void recoverTree(TreeNode* root) { TreeNode *mistake1 = nullptr, *mistake2 = nullptr, *prev = nullptr; inorder(root, mistake1, mistake2, prev); if (mistake1 && mistake2) { // 将错误节点的值暂存 int temp = mistake1->val; mistake1->val = mistake2->val; mistake2->val = temp; } } void inorder(TreeNode* root, TreeNode* &mistake1, TreeNode* &mistake2, TreeNode* &prev) { if (!root) { return; } if (root->left) { inorder(root->left, mistake1, mistake2, prev); // 中序遍历左子节点 } if (prev && root->val < prev->val) { if (!mistake1) { mistake1 = prev; // 一次排序错误 mistake2 = root; }else { mistake2 = root; // 两次排序错误 } } prev = root; // 访问根节点 if (root->right) { inorder(root->right, mistake1, mistake2, prev); // 中序遍历右子节点 } } };
669. 修剪二叉搜索树
给你二叉搜索树的根节点 root ,同时给定最小边界 low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。
- 递归思想:
- 终止条件:当前节点为空
- 递归操作:
- 当前节点小于 low,返回其右子树的修剪结果
- 当前节点大于 high,返回其左子树的修剪结果
- 当前节点值在[low, high]中,左、右子节点等于其对应的修剪结果
- 返回值:根据当前节点与边界值的大小返回对应的节点指针
class Solution { public: TreeNode* trimBST(TreeNode* root, int low, int high) { // 终止条件 if (!root) { return root; } // 当前节点值大于 high,则右子树均不满足条件 if (root->val > high) { return trimBST(root->left, low, high); } if (root->val < low) { return trimBST(root->right, low, high); } // 当前节点值在 [low, high]中 root->left = trimBST(root->left, low, high); root->right = trimBST(root->right, low, high); return root; } };
3. 字典树
208. 实现 Trie (前缀树)
尝试建立一个字典树,支持快速插入单词、查找单词、查找单词前缀的功能。
- 一个字符相当于一个字典树的节点,其含有 26 个子节点指针指向下一个字符
- 单词最后一个字符的节点,设置一个 true 标志着单词的结束
- 不同单词前 N 个相同的字符可以共用该字符节点
- 直接利用类建立前缀树
- 1、建立一个指针指向当前节点
- 2、根据指针查找单词是否存在
- 3、根据查找情况建立新的节点
- 4、将指针指向下一个字符节点
class Trie { public: bool isEnd; vector<Trie *> next; Trie() { isEnd = false; for(int i = 0; i < 26; i++) { next.push_back(nullptr); } } void insert(string word) { Trie *cur = this; // 重要位置 for (char &c : word) { if (cur->next[c - 'a'] == nullptr) { cur->next[c - 'a'] = new Trie(); } cur = cur->next[c - 'a']; } cur->isEnd = true; } bool search(string word) { Trie *cur = this; for (char &c : word) { if (cur->next[c - 'a'] == nullptr) { return false; } cur = cur->next[c - 'a']; } return cur->isEnd; } bool startsWith(string prefix) { Trie *cur = this; for (char &c : prefix) { if (cur->next[c - 'a'] == nullptr) { return false; } cur = cur->next[c - 'a']; } return true; } };
本文作者:GreyWang
本文链接:https://www.cnblogs.com/GreyWang/p/17124733.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步