给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
示例 1:
输入:root = [1,null,2,3]
输出:[1,3,2]
示例 2:
输入:root = []
输出:[]
示例 3:
输入:root = [1]
输出:[1]
栈解决问题,实例代码如下:
// 迭代版本
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> stk;
vector<int> res;
while (root || !stk.empty()) {
while (root) {
stk.push(root);
root = root->left;
}
if (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
res.emplace_back(node->val);
root = node->right;
}
}
return res;
}
};
// 递归版本
class Solution {
public:
void core(TreeNode* root) {
if (!root) {
return;
}
core(root->left);
res.push_back(root->val);
core(root->right);
}
vector<int> inorderTraversal(TreeNode* root) {
core(root);
return res;
}
private:
vector<int> res;
};
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
二叉树中序遍历是有序的,可以通过这一点来判断二叉树的正确性,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 迭代版本
class Solution {
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> stk;
int cur = INT_MIN;
// 设置flag主要原因是怕root的取值就是INT_MIN
bool flag = false; // 首次进入标志位判断
while (root || !stk.empty()) {
while (root) {
stk.push(root);
root = root->left;
}
if (!stk.empty()) {
auto node = stk.top();
int value = node->val;
if (value <= cur && flag) {
return false;
}
cur = value;
stk.pop();
root = node->right;
flag = true;
}
}
return true;
}
};
// 递归版本,先序遍历
class Solution {
public:
bool helper(TreeNode* root, long long lower, long long upper) {
if (root == nullptr) {
return true;
}
if (root -> val <= lower || root -> val >= upper) {
return false;
}
return helper(root -> left, lower, root -> val) && helper(root -> right, root -> val, upper);
}
bool isValidBST(TreeNode* root) {
return helper(root, LONG_MIN, LONG_MAX);
}
};
时间复杂度: O ( n ) O(n) O(n),n为树节点个数
空间复杂度: O ( n ) O(n) O(n)
初始遍历时,状态的判定:
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
常规递归和迭代两种思路,示例代码如下,
递归:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return dfs(root->left, root->right);
}
bool dfs(TreeNode* node1, TreeNode* node2) {
if (!node1 && !node2) {
return true;
}
if (!node1 || !node2) {
return false;
}
if (node1->val != node2-> val) {
return false;
}
return dfs(node1->left, node2->right) && dfs(node1->right, node2->left);
}
};
迭代:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root) {
return true;
}
stack<TreeNode*> stk;
stk.push(root->left);
stk.push(root->right);
while (!stk.empty()) {
auto top1 = stk.top();
stk.pop();
auto top2 = stk.top();
stk.pop();
if (!top1 && !top2) {
continue;
}
if (!top1 || !top2 || top1->val != top2->val) {
return false;
}
stk.push(top1->left);
stk.push(top2->right);
stk.push(top1->right);
stk.push(top2->left);
}
return true;
}
};
时间复杂度: O ( n ) O(n) O(n),n为节点个数
空间复杂度: O ( n ) O(n) O(n)
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
简单题目,直接上代码:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 迭代
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (!root) {
return res;
}
vector<int> tmp;
queue<TreeNode*> q;
q.push(root);
int this_layer_node = 1;
int next_layer_node = 0;
while (!q.empty()) {
TreeNode* top = q.front();
q.pop();
if (top->left) {
q.push(top->left);
++next_layer_node;
}
if (top->right) {
q.push(top->right);
++next_layer_node;
}
--this_layer_node;
tmp.emplace_back(top->val);
if (this_layer_node == 0) {
this_layer_node = next_layer_node;
next_layer_node = 0;
res.emplace_back(tmp);
tmp = vector<int>();
}
}
return res;
}
};
// 递归
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if (!root) {
return res;
}
core(root, 1);
return res;
}
void core(TreeNode* root, int level) {
if (!root) {
return;
}
if (res.size() < level) {
res.push_back(vector<int>());
}
res[level - 1].push_back(root->val);
if (root->left) {
core(root->left, level + 1);
}
if (root->right) {
core(root->right, level + 1);
}
}
private:
vector<vector<int>> res;
};
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
明确两层交接处的逻辑
给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
输入:
3
/ \
9 20
/ \
15 7
输出:
[
[3],
[20,9],
[15,7]
]
层次遍历的变种,每遍历一层时根据层数的奇偶不同按照不同的顺序将本层数据插入至deque中,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 迭代
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>> res;
if (!root) {
return res;
}
queue<TreeNode*> q;
q.push(root);
bool is_order_left = true;
while (!q.empty()) {
deque<int> dq;
int length = q.size();
for (int i = 0; i < length; ++i) {
TreeNode* node = q.front();
q.pop();
if (is_order_left) {
dq.push_back(node->val);
} else {
dq.push_front(node->val);
}
if (node->left) {
q.push(node->left);
}
if (node->right) {
q.push(node->right);
}
}
res.emplace_back(vector<int>{dq.begin(), dq.end()});
is_order_left = !is_order_left;
}
return res;
}
};
// 递归
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
if (root) {
core(root, 1, true);
}
vector<vector<int>> f_res;
for (auto elem: res) {
f_res.push_back(vector<int>(elem.begin(), elem.end()));
}
return f_res;
}
void core(TreeNode* root, int level, bool o_l) {
if (!root) {
return;
}
if (res.size() < level) {
res.push_back(list<int>());
}
if (o_l) {
res[level - 1].push_back(root->val);
} else {
res[level - 1].push_front(root->val);
}
if (root->left) {
core(root->left, level + 1, !o_l);
}
if (root->right) {
core(root->right, level + 1, !o_l);
}
}
private:
vector<list<int>> res;
};
时间: O ( n ) O(n) O(n),虽然是两层循环,但是只完成了树的一次遍历
空间: O ( n ) O(n) O(n)
明确两层交接处的逻辑
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
本题与【543. 二叉树的直径】较为相似,示例代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 递归
class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root) {
return 0;
}
int left = maxDepth(root->left);
int right = maxDepth(root->right);
return max(left, right) + 1;
}
};
// 迭代
class Solution {
public:
int maxDepth(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
int ans = 0;
while (!q.empty()) {
int length = q.size();
while (length) {
auto top = q.front();
q.pop();
if (top->left) {
q.push(top->left);
}
if (top->right) {
q.push(top->right);
}
--length;
}
++ans;
}
return ans;
}
};
时间复杂度: O ( n ) O(n) O(n),n为树节点个数
空间复杂度: O ( n ) O(n) O(n)
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
理解前序遍历和中序遍历的过程,即可完成目标,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int length = preorder.size();
pre = preorder;
in = inorder;
for (int i = 0; i < length; ++i) {
m[inorder[i]] = i;
}
return core(0, length - 1, 0, length - 1);
}
TreeNode* core(int pre_begin, int pre_end, int in_begin, int in_end) {
if (pre_begin > pre_end) {
return nullptr;
}
int k = m[pre[pre_begin]] - in_begin;
TreeNode* root = new TreeNode(pre[pre_begin]);
root->left = core(pre_begin + 1, pre_begin + k, in_begin, in_begin + k - 1);
root->right = core(pre_begin + k + 1, pre_end, in_begin + k + 1, in_end);
return root;
}
private:
vector<int> pre;
vector<int> in;
unordered_map<int, int> m;
};
时间: O ( n ) O(n) O(n)
空间: O ( n ) O(n) O(n),最坏情况下树是一条链表,这时需要O(n)的栈空间存储中间状态。
迭代法,示例代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.empty()) {
return nullptr;
}
stack<TreeNode*> stk;
int pre = 0, in = 0;
TreeNode* cur_root = new TreeNode(preorder[pre]);
auto root = cur_root;
stk.push(cur_root);
++pre;
while (pre < preorder.size()) {
if (cur_root->val == inorder[in]) {
while (!stk.empty() && stk.top()->val == inorder[in]) {
cur_root = stk.top();
stk.pop();
++in;
}
cur_root->right = new TreeNode(preorder[pre]);
cur_root = cur_root->right;
} else {
cur_root->left = new TreeNode(preorder[pre]);
cur_root = cur_root->left;
}
stk.push(cur_root);
++pre;
}
return root;
}
};
向下走是入栈的过程,向上回溯是出栈的过程,出栈的过程才是前序遍历和中序遍历对齐的时候。
根据一棵树的中序遍历与后序遍历构造二叉树,你可以假设树中没有重复的元素。
例如,给出中序遍历 inorder = [9,3,15,20,7],后序遍历 postorder = [9,15,7,20,3],返回如下的二叉树:
3
/ \
9 20
/ \
15 7
由于后续遍历从后到前一直都是某棵树的根节点,因而没有必要向<重建二叉树>中通过距离来构建子树,直接构建树即可,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int length = inorder.size();
for (int i = 0; i < length; ++i) {
m[inorder[i]] = i;
}
post_idx = length - 1;
return core(postorder, 0, length - 1);
}
TreeNode* core(vector<int>& postorder, int left, int right) {
if (left > right) {
return nullptr;
}
int root_val = postorder[post_idx];
int index = m[root_val];
TreeNode* root = new TreeNode(root_val);
--post_idx;
// 从后向前遍历,先构建右子树,之后再构建左子树
root->right = core(postorder, index + 1, right);
root->left = core(postorder, left, index - 1);
return root;
}
private:
int post_idx = 0;
unordered_map<int, int> m;
};
时间:O(n)
空间:O(n)
理解后序遍历序列的构成逻辑,从后向前依次是根节点->右节点->左节点
迭代法,示例代码如下,具体思路和<105. 从前序与中序遍历序列构造二叉树>一样,只不过过程是反的。
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (postorder.size() == 0) {
return nullptr;
}
auto root = new TreeNode(postorder[postorder.size() - 1]);
auto s = stack<TreeNode*>();
s.push(root);
int inorderIndex = inorder.size() - 1;
for (int i = int(postorder.size()) - 2; i >= 0; i--) {
int postorderVal = postorder[i];
auto node = s.top();
if (node->val != inorder[inorderIndex]) {
node->right = new TreeNode(postorderVal);
s.push(node->right);
} else {
while (!s.empty() && s.top()->val == inorder[inorderIndex]) {
node = s.top();
s.pop();
inorderIndex--;
}
node->left = new TreeNode(postorderVal);
s.push(node->left);
}
}
return root;
}
};
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9]
or
二分查找加先序构建二叉树得到最终结果,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* core(vector<int>& nums, int low, int high) {
if (low > high || low < 0 || high >= nums.size()) {
return nullptr;
}
int mid = (low + high) / 2;
int val = nums[mid];
TreeNode* root = new TreeNode(val);
root->left = core(nums, low, mid - 1);
root->right = core(nums, mid + 1, high);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
int low = 0;
int high = nums.size() - 1;
return core(nums, low, high);
}
};
时间: O ( n ) O(n) O(n)
空间: O ( l o g n ) O(logn) O(logn)
边界条件的判断
迭代方式进行预填充,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if (nums.size() == 0) return nullptr;
TreeNode* root = new TreeNode(0); // 初始根节点
queue<TreeNode*> nodeQue; // 放遍历的节点
queue<int> leftQue; // 保存左区间下标
queue<int> rightQue; // 保存右区间下标
nodeQue.push(root); // 根节点入队列
leftQue.push(0); // 0为左区间下标初始位置
rightQue.push(nums.size() - 1); // nums.size() - 1为右区间下标初始位置
while (!nodeQue.empty()) {
TreeNode* curNode = nodeQue.front();
nodeQue.pop();
int left = leftQue.front(); leftQue.pop();
int right = rightQue.front(); rightQue.pop();
int mid = (left + right) / 2;
curNode->val = nums[mid]; // 将mid对应的元素给中间节点
if (left <= mid - 1) { // 处理左区间
curNode->left = new TreeNode(0);
nodeQue.push(curNode->left);
leftQue.push(left);
rightQue.push(mid - 1);
}
if (right >= mid + 1) { // 处理右区间
curNode->right = new TreeNode(0);
nodeQue.push(curNode->right);
leftQue.push(mid + 1);
rightQue.push(right);
}
}
return root;
}
};
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。说明:叶子节点是指没有子节点的节点。
与【104. 二叉树的最大深度】不同的是,这里需要识别出这个节点是否是叶子节点,这样才能正常得到二叉树的最小深度。示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 递归版本
class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) {
return 0;
}
if (!root->left && !root->right) {
return 1;
}
int min_left = INT_MAX;
if (root->left) {
min_left = minDepth(root->left);
}
int min_right = INT_MAX;;
if (root->right) {
min_right = minDepth(root->right);
}
return 1 + min(min_left, min_right);
}
};
// 迭代版本
class Solution {
public:
int minDepth(TreeNode* root) {
if (!root) {
return 0;
}
queue<pair<TreeNode*, int>> q;
q.emplace(root, 1);
while (!q.empty()) {
auto node = q.front().first;
int depth = q.front().second;
q.pop();
if (!node->left && !node->right) {
return depth;
}
if (node->left) {
q.emplace(node->left, depth + 1);
}
if (node->right) {
q.emplace(node->right, depth + 1);
}
}
return 0;
}
};
时间: O ( n ) O(n) O(n),n代表的是节点个数。
空间: O ( n ) O(n) O(n)
深度遍历过程中,遇到的第一个叶子节点,对应的深度就是最小深度
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum。叶子节点 是指没有子节点的节点。
先序遍历树过程中得到最终结果,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 递归版本
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (!root) {
return false;
}
if (!root->left && !root->right) {
return targetSum == root->val;
}
return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}
};
// 迭代版本
class Solution {
public:
bool hasPathSum(TreeNode *root, int sum) {
if (root == nullptr) {
return false;
}
queue<pair<TreeNode*, int>> que_node;
que_node.emplace(root, root->val);
while (!que_node.empty()) {
TreeNode *now = que_node.front().first;
int temp = que_node.front().second;
que_node.pop();
if (now->left == nullptr && now->right == nullptr) {
if (temp == sum) {
return true;
}
continue;
}
if (now->left != nullptr) {
que_node.emplace(now->left, now->left->val + temp);
}
if (now->right != nullptr) {
que_node.emplace(now->right, now->right->val + temp);
}
}
return false;
}
};
时间复杂度: O ( n ) O(n) O(n),n代表的是节点个数。
空间复杂度: O ( h ) O(h) O(h),h代表的是树的高度。
树从上到下 逐级累加 或者 逐级递减
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
中序遍历解决问题,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void dfs(TreeNode* root, vector<int>& cur_path, int sum_now, int targetSum) {
if(!root) {
return;
}
sum_now += root->val;
cur_path.emplace_back(root->val);
bool is_leaf = !root->left && !root->right;
if (is_leaf && sum_now == targetSum) {
res.emplace_back(cur_path);
}
if (!is_leaf) {
dfs(root->left, cur_path, sum_now, targetSum);
dfs(root->right, cur_path, sum_now, targetSum);
}
cur_path.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<int> cur_path;
int sum_now = 0;
dfs(root, cur_path, sum_now, targetSum);
return res;
}
private:
vector<vector<int>> res;
};
// 迭代版本
class Solution {
public:
vector<vector<int>> ret;
unordered_map<TreeNode*, TreeNode*> parent;
void getPath(TreeNode* node) {
vector<int> tmp;
while (node != nullptr) {
tmp.emplace_back(node->val);
node = parent[node];
}
reverse(tmp.begin(), tmp.end());
ret.emplace_back(tmp);
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
if (root == nullptr) {
return ret;
}
queue<TreeNode*> que_node;
queue<int> que_sum;
que_node.emplace(root);
que_sum.emplace(0);
while (!que_node.empty()) {
TreeNode* node = que_node.front();
que_node.pop();
int rec = que_sum.front() + node->val;
que_sum.pop();
if (node->left == nullptr && node->right == nullptr) {
if (rec == targetSum) {
getPath(node);
}
} else {
if (node->left != nullptr) {
parent[node->left] = node;
que_node.emplace(node->left);
que_sum.emplace(rec);
}
if (node->right != nullptr) {
parent[node->right] = node;
que_node.emplace(node->right);
que_sum.emplace(rec);
}
}
}
return ret;
}
};
时间复杂度: O ( n ∗ h + N ) O(n*h + N) O(n∗h+N),树的叶子节点个数为n,树的总结点数为N,每个叶子节点都会被遍历到,树的深度为h,树节点遍历本身的复杂度为 O ( N ) O(N) O(N),答案填充的过程时间复杂度为O(n*h + N)。
空间复杂度: O ( n ) O(n) O(n),最坏情况辅助结果存下整棵树的所有节点。
给你二叉树的根结点 root ,请你将它展开为一个单链表:展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。
先序遍历过程解决当前问题,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void flatten(TreeNode* root) {
if (!root) {
return;
}
stack<TreeNode*> stk;
stk.push(root);
TreeNode* prev = nullptr;
while (!stk.empty()) {
auto cur = stk.top();
stk.pop();
if (prev) {
prev->left = nullptr;
prev->right = cur;
}
// 先处理cur,再将prev赋值为cur
prev = cur;
// 先push right再push left,保证stk弹出的下一个节点永远都是top节点的左子节点,除非左子节点为空,从而保证先序遍历
if (cur->right) {
stk.push(cur->right);
}
if (cur->left) {
stk.push(cur->left);
}
}
}
};
时间复杂度: O ( n ) O(n) O(n),遍历整个树的时间
空间复杂度: O ( n ) O(n) O(n),栈的大小
在前序遍历中嵌入节点指针的变化情况
寻找前驱节点,解释见 这里,示例代码如下:
class Solution {
public:
void flatten(TreeNode* root) {
auto cur = root;
while (cur) {
if (cur->left) {
auto next = cur->left;
auto pre = next;
while (pre->right) {
pre = pre->right;
}
pre->right = cur->right;
cur->left = nullptr;
cur->right = next;
}
cur = cur->right;
}
}
};
// 递归写法
class Solution {
public:
void flatten(TreeNode* root) {
if (!root) {
return;
}
flatten(root->left);
flatten(root->right);
if (root->left) {
auto pre = root->left;
while (pre->right) {
pre = pre->right;
}
pre->right = root->right;
root->right = root->left;
root->left = nullptr;
}
root = root->right;
}
};
时间复杂度: O ( n ) O(n) O(n),遍历整个树的时间
空间复杂度: O ( 1 ) O(1) O(1)
理解前驱节点的含义,遍历完前驱节点后,会直接遍历右子树,所以会先直接把右子树挂在前驱节点的右节点,然后直接把左子树当作右子树。
给定一个 完美二叉树 ,填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。初始状态下,所有 next 指针都被设置为 NULL。
层次遍历过程中创建右侧节点指针,示例代码如下所示:
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
if (!root) {
return nullptr;
}
queue<Node*> q;
q.push(root);
int this_layer_node = 1;
int next_layer_node = 0;
while (!q.empty()) {
auto top = q.front();
q.pop();
if (top->left) {
q.push(top->left);
++next_layer_node;
}
if (top->right) {
q.push(top->right);
++next_layer_node;
}
--this_layer_node;
if (this_layer_node == 0) {
top->next = nullptr;
this_layer_node = next_layer_node;
next_layer_node = 0;
} else {
top->next = q.front();
}
}
return root;
}
};
时间: O ( n ) O(n) O(n)
空间: O ( n ) O(n) O(n)
利用不同的next指针构建next体系,题解见 这里,示例代码如下:
class Solution {
public:
Node* connect(Node* root) {
if (!root) {
return NULL;
}
auto l_m = root;
while (l_m->left) {
auto head = l_m;
while (head) {
if (head->left) {
head->left->next = head->right;
}
if (head->next) {
head->right->next = head->next->left;
}
head = head->next;
}
l_m = l_m->left;
}
return root;
}
};
// 递归版本
class Solution {
public:
Node* connect(Node* root) {
if (!root || !root->left) {
return root;
}
root->left->next = root->right;
if (root->next) {
root->right->next = root->next->left;
}
connect(root->left);
connect(root->right);
return root;
}
};
时间: O ( n ) O(n) O(n)
空间: O ( 1 ) O(1) O(1)
明确next的两种情况
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
示例 1:
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
递归解决问题。遍历过程中记录以该节点为根节点且有一条路径经过该节点的最大路径和cur_sum,如果cur_sum比max_sum大则更新max_sum,递归返回以该节点为根节点的一条路径(以该节点为终点)的最大路径和,这样做的合理性是通过两个地方保证的:
示例代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int core(TreeNode* root) {
if (!root) {
return 0;
}
int left = max(0, core(root->left));
int right = max(0, core(root->right));
res = max(left + right + root->val, res);
return root->val + max(left, right);
}
int maxPathSum(TreeNode* root) {
core(root);
return res;
}
private:
int res = INT_MIN;
};
// 迭代坐竹筏
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxPathSum(TreeNode* root) {
if (!root) {
return 0;
}
unordered_map<TreeNode*, int> m;
int res = INT_MIN;
// 给map赋默认值非常重要,这决定了后续遍历会不会死循环
m[nullptr] = 0;
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
auto node = stk.top();
stk.pop();
if (m.count(node->left) && m.count(node->right)) {
int max_l = max(0, m[node->left]);
int max_r = max(0, m[node->right]);
m[node] = max(max_l, max_r) + node->val;
res = max(res, node->val + max_l + max_r);
} else {
// 保存现场很重要
stk.push(node);
if (node->right) {
stk.push(node->right);
}
if (node->left) {
stk.push(node->left);
}
}
}
return res;
}
private:
int res = INT_MIN;
};
时间复杂度:后序遍历时间复杂度 O ( n ) O(n) O(n)
空间复杂度:辅助空间 O ( n ) O(n) O(n)
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。计算从根节点到叶节点生成的 所有数字之和 。
示例 1:
输入:root = [1,2,3]
输出:25
解释:
从根到叶子节点路径 1->2 代表数字 12
从根到叶子节点路径 1->3 代表数字 13
因此,数字总和 = 12 + 13 = 25
先序遍历解决问题,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void dfs(TreeNode* root, int cur_sum) {
if (!root) {
return;
}
cur_sum = cur_sum * 10 + root->val;
bool is_leaf = !root->left && !root->right;
if (is_leaf) {
res += cur_sum;
} else {
dfs(root->left, cur_sum);
dfs(root->right, cur_sum);
}
}
int sumNumbers(TreeNode* root) {
int cur_sum = 0;
dfs(root, cur_sum);
return res;
}
private:
int res;
};
// 迭代版本
class Solution {
public:
int sumNumbers(TreeNode* root) {
if (!root) {
return 0;
}
int res = 0;
queue<pair<TreeNode*, int>> q;
q.emplace(root, root->val);
while (!q.empty()) {
int num = q.front().second;
auto node = q.front().first;
q.pop();
if (!node->left && !node->right) {
res += num;
continue;
}
if (node->left) {
q.emplace(node->left, num * 10 + node->left->val);
}
if (node->right) {
q.emplace(node->right, num * 10 + node->right->val);
}
}
return res;
}
};
时间复杂度: O ( n ) O(n) O(n),n为树节点的个数
空间复杂度: O ( n ) O(n) O(n)
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例:
输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:
1 <---
/ \
2 3 <---
\ \
5 4 <---
层次遍历过程中记录每一层的最右侧节点,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 迭代版本
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> result;
if (!root) {
return result;
}
queue<TreeNode*> q;
q.push(root);
int this_layer_node = 1;
int next_layer_node = 0;
while (!q.empty()) {
auto top = q.front();
q.pop();
if (top->left) {
q.push(top->left);
++next_layer_node;
}
if (top->right) {
q.push(top->right);
++next_layer_node;
}
--this_layer_node;
if (this_layer_node == 0) {
result.emplace_back(top->val);
this_layer_node = next_layer_node;
next_layer_node = 0;
}
}
return result;
}
};
// 递归版本 -> 前序遍历覆盖map取值
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
if (!root) {
return res;
}
pre_order(root, 0);
for (int i = 0; i <= max_depth; ++i) {
res.push_back(m[i]);
}
return res;
}
void pre_order(TreeNode* root, int depth) {
if (!root) {
return ;
}
m[depth] = root->val;
max_depth = max(max_depth, depth);
pre_order(root->left, depth + 1);
pre_order(root->right, depth + 1);
}
private:
unordered_map<int, int> m;
vector<int> res;
int max_depth = 0;
};
时间: O ( n ) O(n) O(n),n代表的是节点个数。
空间: O ( n ) O(n) O(n)。
正确识别两层交界处的状态
请你实现 Trie 类:
Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
示例:
输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]
解释
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
示例代码如下所示:
class Trie {
public:
Trie(): children(26), is_end(false) {}
Trie* search_prefix(string prefix) {
Trie* node = this;
for (char ch: prefix) {
ch -= 'a';
if (node->children[ch] == nullptr) {
return nullptr;
}
node = node->children[ch];
}
return node;
}
void insert(string word) {
Trie* node = this;
for (char ch: word) {
ch -= 'a';
if (node->children[ch] == nullptr) {
node->children[ch] = new Trie();
}
node = node->children[ch];
}
node->is_end = true;
}
bool search(string word) {
Trie* node = this->search_prefix(word);
return node != nullptr && node->is_end;
}
bool startsWith(string prefix) {
return this->search_prefix(prefix) != nullptr;
}
private:
vector<Trie*> children;
bool is_end;
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h 个节点。
如果一个节点的左子树和右子树深度相等,则代表左子树为满二叉树,且右子树为非满二叉树;如果不相等,则左子树深度一定比右子树深度大,且左子树为非满二叉树,右子树为满二叉树,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// #include
class Solution {
public:
int get_level(TreeNode* root) {
int levels = 0;
while (root) {
levels += 1;
root = root->left;
}
return levels;
}
int countNodes(TreeNode* root) {
if (!root) {
return 0;
}
int left_level = get_level(root->left);
int right_level = get_level(root->right);
if (left_level == right_level) {
return countNodes(root->right) + pow(2, left_level);
} else {
return countNodes(root->left) + pow(2, right_level);
}
}
};
// 迭代方法
class Solution {
public:
// 求二叉树的深度
int countLevels(TreeNode* root) {
int levels = 0;
while (root) {
root = root->left; levels += 1;
}
return levels;
}
/*
* 功能: 判断最后一层第index个索引是否存在
* root: 二叉树根节点
* index:判断最后一层索引为index的节点是否存在, 索引范围是[1, 2^depth]
* depth:倒数第二层的深度, 这是因为满二叉树最后一层的节点数等于 2^depth
*/
bool is_exist(TreeNode* root, int index, int depth) {
TreeNode* node = root;
while (depth) {
// 最后一层分界线
int mid = ((1 << depth) >> 1);
if (index > mid) {
// 如果在右子树,需要更新索引值
index -= mid;
node = node->right;
}
else {
node = node->left;
}
depth -= 1;
}
return node != nullptr;
}
int countNodes(TreeNode* root) {
// 3. 二分查找
if (root == nullptr) return 0;
// 二叉树深度
int depth = countLevels(root);
// 倒数第二层深度
int depth_prev = depth - 1;
int start = 1, end = (1 << depth_prev), mid = 0;
while (start <= end) {
mid = start + ((end - start) >> 1);
if (is_exist(root, mid, depth_prev)) start = mid + 1;
else end = mid - 1;
}
// start - 1为最后一层节点数
int ret = (1 << depth_prev) - 1 + start - 1;
return ret;
}
};
时间复杂度: O ( [ l o g n ] 2 ) O([logn] ^ 2) O([logn]2),这里不需要计算每个节点的深度,因为每次只是计算一半节点的深度,每个节点的深度计算需要消耗O(logn)的时间复杂度,总共会遍历O(logn)个节点,因而总共时间复杂度为 O ( [ l o g n ] 2 ) O([logn] ^2) O([logn]2)。
空间复杂度:个人理解 O ( l o g n ) O(logn) O(logn),这个是堆栈深度。
理解完全二叉树的定义,从而懂得如何节省计算量。
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
递归解决问题,示例代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
// 递归版本
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root) {
return root;
}
// 交换
TreeNode* tmp = root->right;
root->right = root->left;
root->left = tmp;
// 递归
root->right = invertTree(root->right);
root->left = invertTree(root->left);
return root;
}
};
// 迭代版本
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root) {
return nullptr;
}
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
auto node = stk.top();
stk.pop();
if (node->left) {
stk.push(node->left);
}
if (node->right) {
stk.push(node->right);
}
auto temp = node->left;
node->left = node->right;
node->right = temp;
}
return root;
}
};
时间复杂度: O ( n ) O(n) O(n),n为节点个数
空间复杂度: O ( n ) O(n) O(n)
理解翻转的含义,是左子树和右子树进行翻转
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
和<二叉搜索树的第K大节点>一模一样,只不过是正常的中序遍历,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 迭代形式
class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
stack<TreeNode*> stk;
while (root || !stk.empty()) {
while (root) {
stk.push(root);
root = root->left;
}
if (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
--k;
if (k == 0) {
return node->val;
}
root = node->right;
}
}
return -1;
}
};
// 递归形式
class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
_k = k;
dfs(root);
return res;
}
void dfs(TreeNode* root) {
if(!root) {
return;
}
dfs(root->left);
--_k;
if (_k == 0) {
res = root->val;
}
dfs(root->right);
}
private:
int res;
int _k;
};
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
根据左右子树节点个数来判断要遍历左子树还是右子树,找到目标节点,示例代码如下所示:
class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
t_c(root);
return kthSmallest_core(root, k);
}
int t_c(TreeNode* root) {
if (!root) {
return 0;
}
int c_l = t_c(root->left);
int c_r = t_c(root->right);
cntr[root] = 1 + c_l + c_r;
return cntr[root];
}
int kthSmallest_core(TreeNode* root, int k) {
auto node = root;
while (node) {
int left = 0;
if (node->left) {
left = cntr[node->left];
}
if (left < k - 1) {
node = node->right;
k -= left + 1;
} else if (left == k - 1) {
return node->val;
} else {
node = node->left;
}
}
return -1;
}
private:
unordered_map<TreeNode*, int> cntr;
TreeNode* _root;
};
时间: O ( n ) O(n) O(n)
空间: O ( n ) O(n) O(n)
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
示例 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的val都小于root->val,证明目标节点在左子树;如果p和q的val都大于root->val,证明目标节点在右子树;其他情况下,root就是目标节点。示例代码如下所示:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
auto ancestor = root;
while (true) {
if (p->val < ancestor->val && q->val < ancestor->val) {
ancestor = ancestor->left;
} else if (p->val > ancestor->val && q->val > ancestor->val) {
ancestor = ancestor->right;
} else {
break;
}
}
return ancestor;
}
};
// 繁琐版本
class Solution {
public:
vector<TreeNode*> getPath(TreeNode* root, TreeNode* target) {
vector<TreeNode*> path;
TreeNode* node = root;
while (node != target) {
path.push_back(node);
if (target->val < node->val) {
node = node->left;
}
else {
node = node->right;
}
}
path.push_back(node);
return path;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector<TreeNode*> path_p = getPath(root, p);
vector<TreeNode*> path_q = getPath(root, q);
TreeNode* ancestor;
for (int i = 0; i < path_p.size() && i < path_q.size(); ++i) {
if (path_p[i] == path_q[i]) {
ancestor = path_p[i];
}
else {
break;
}
}
return ancestor;
}
};
时间: O ( n ) O(n) O(n)
空间: O ( 1 ) O(1) O(1)
理解二叉搜索树的性质,左子树的节点值都比root小,右子树的节点值都比root大。
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
最常规思路,先获取root到两个节点的路径path1和path2,然后再找到path1和path2的分叉点,即完成了任务。示例代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool find_path(TreeNode* root, TreeNode* node, vector<TreeNode*>& path) {
if (!root || !node) {
return false;
}
// push的是root,而不是node
path.emplace_back(root);
if (root == node) {
return true;
}
if (find_path(root->left, node, path)) {
return true;
}
if (find_path(root->right, node, path)) {
return true;
}
path.pop_back();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector<TreeNode*> p_path;
vector<TreeNode*> q_path;
find_path(root, p, p_path);
find_path(root, q, q_path);
int length_p = p_path.size();
int length_q = q_path.size();
int length = max(length_p, length_q);
for (int i = 1; i < length; ++i) {
if (i == length_p || i == length_q || p_path[i] != q_path[i]) {
return p_path[i - 1];
}
}
return root;
}
};
时间复杂度: O ( n ) O(n) O(n),n为树节点个数
空间复杂度: O ( n ) O(n) O(n)
不用path辅助空间,直接遍历解决问题,示例代码如下:
class Solution {
public:
bool core(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root) {
return false;
}
bool lson = core(root->left, p, q);
bool rson = core(root->right, p, q);
if ((lson && rson) || (root->val == p->val || root->val == q->val) && (lson || rson)) {
ans = root;
}
return lson || rson || root->val == p->val || root->val == q->val;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
core(root, p, q);
return ans;
}
private:
TreeNode* ans;
};
时间: O ( n ) O(n) O(n)
空间: O ( n ) O(n) O(n)
(lson && rson) || (root->val == p->val || root->val == q->val) && (lson || rson)
给定一个二叉树,返回所有从根节点到叶子节点的路径。
输入:
1
/ \
2 3
\
5
输出: ["1->2->5", "1->3"],解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3
正常树先序遍历过程,中间添加叶子节点判断,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 递归形式
class Solution {
public:
void dfs(TreeNode* root, string cur_path) {
if (!root) {
return;
}
string root_str = cur_path + to_string(root->val);
bool is_leaf = !root->left && !root->right;
if (!is_leaf) {
// "->" -> string, '->' -> char
dfs(root->left, root_str + "->");
dfs(root->right, root_str + "->");
} else {
res.emplace_back(root_str);
}
}
vector<string> binaryTreePaths(TreeNode* root) {
string cur_path;
dfs(root, cur_path);
return res;
}
private:
vector<string> res;
};
// 迭代形式
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> res;
if (!root) {
return res;
}
queue<pair<TreeNode*, string>> q;
q.emplace(root, to_string(root->val));
while (!q.empty()) {
auto node = q.front().first;
string str = q.front().second;
q.pop();
if (!node->left && !node->right) {
res.push_back(str);
} else {
if (node->left) {
q.emplace(node->left, str + "->" + to_string(node->left->val));
}
if (node->right) {
q.emplace(node->right, str + "->" + to_string(node->right->val));
}
}
}
return res;
}
};
如果字符串的拷贝也算时间和空间复杂度的话,则最坏情况下树是一条链表,字符串的拷贝需要 ∑ i = 1 2 i = O ( n 2 ) \sum_{i=1}^2 i=O(n^2) ∑i=12i=O(n2)的时间和空间复杂度,因为时间和空间复杂度均为 O ( n 2 ) O(n^2) O(n2)。
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
示例 1:
输入:root = [1,2,3,null,null,4,5]
输出:[1,2,3,null,null,4,5]
先序遍历解决问题,示例代码如下所示:
class Codec {
public:
string serialize(TreeNode* root) {
string res;
core_s(root, res);
return res;
}
// 一定要重视引用的作用,否则会返回空字符串
void core_s(TreeNode* root, string& res) {
if (!root) {
res += "null,";
return;
}
res += to_string(root->val) + ",";
core_s(root->left, res);
core_s(root->right, res);
}
TreeNode* deserialize(string data) {
string str;
for (auto c: data) {
if (c == ',') {
cache.push_back(str);
str.clear();
} else {
str.push_back(c);
}
}
if (!str.empty()) {
cache.push_back(str);
}
return core_d();
}
TreeNode* core_d() {
if (cache[idx] == "null") {
++idx;
return nullptr;
}
TreeNode* root = new TreeNode(stoi(cache[idx]));
++idx;
root->left = core_d();
root->right = core_d();
return root;
}
private:
int idx = 0;
vector<string> cache;
};
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
dfs解决问题。用两个map存储选择这个节点和不选择这个节点时从这个节点出发能够抢到的最大财物,递归完成后max(chosen_map[root], not_chosen_map[root])即为所求结果。示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void dfs(TreeNode* root) {
if (!root) {
return;
}
dfs(root->left);
dfs(root->right);
chosen_map[root] = root->val + not_chosen_map[root->left] + not_chosen_map[root->right];
not_chosen_map[root] = max(chosen_map[root->left], not_chosen_map[root->left]) + max(chosen_map[root->right], not_chosen_map[root->right]);
}
int rob(TreeNode* root) {
if (!root) {
return 0;
}
dfs(root);
return max(chosen_map[root], not_chosen_map[root]);
}
private:
unordered_map<TreeNode*, int> chosen_map;
unordered_map<TreeNode*, int> not_chosen_map;
};
// 节省动态规划辅助空间开销
class Solution {
public:
pair<int, int> core(TreeNode* root) {
if (!root) {
return {0, 0};
}
auto l = core(root->left);
auto r = core(root->right);
int s = root->val + l.first + r.first;
int n_s = max(l.first, l.second) + max(r.first, r.second);
return {n_s, s};
}
int rob(TreeNode* root) {
auto res = core(root);
return max(res.first, res.second);
}
};
时间复杂度: O ( n ) O(n) O(n),二叉树后序遍历的时间复杂度为 O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
理解是否选择本节点后,具体的取值操作
计算给定二叉树的所有左叶子之和。
示例:
3
/ \
9 20
/ \
15 7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
先序遍历过程中添加记录左叶子节点的值,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void dfs(TreeNode* root, bool from_left) {
if (!root) {
return;
}
bool is_leaf = !root->left && !root->right;
if (is_leaf && from_left) {
res += root->val;
return;
}
dfs(root->left, true);
dfs(root->right, false);
}
int sumOfLeftLeaves(TreeNode* root) {
bool from_left = false;
dfs(root, from_left);
return res;
}
private:
int res;
};
// 迭代版本
class Solution {
public:
bool is_leaf(TreeNode* root) {
if (!root) {
return false;
}
return !root->left && !root->right;
}
int sumOfLeftLeaves(TreeNode* root) {
if (!root) {
return 0;
}
queue<TreeNode*> q;
q.push(root);
int res = 0;
while (!q.empty()) {
auto node = q.front();
q.pop();
if (node->left) {
if (is_leaf(node->left)) {
res += node->left->val;
} else {
q.push(node->left);
}
}
if (node->right) {
if (!is_leaf(node->right)) {
q.push(node->right);
}
}
}
return res;
}
};
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
判断当前节点是否是父节点的左子节点
给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
示例:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
10
/ \
5 -3
/ \ \
3 2 11
/ \ \
3 -2 1
返回 3。和等于 8 的路径有:
1. 5 -> 3
2. 5 -> 2 -> 1
3. -3 -> 11
这个题明确提出路径前缀和以及hash_map组合解决问题。记录每条路径的累积和,如果发现当前累积和-target在之前出现过,那么之前的路径集合中一定存在==target的路径,示例代码如下所示。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int pathSum(TreeNode* root, int targetSum) {
m[0] = 1;
core(0, root, targetSum);
return res;
}
void core(long cur_sum, TreeNode* root, int targetSum) {
if (!root) {
return;
}
cur_sum += root->val;
if (m.find(cur_sum - targetSum) != m.end()) {
res += m[cur_sum - targetSum];
}
++m[cur_sum];
core(cur_sum, root->left, targetSum);
core(cur_sum, root->right, targetSum);
--m[cur_sum];
}
private:
// 可能会溢出,因而map的key被定义为long型
unordered_map<long, int> m;
int res = 0;
};
// 迭代
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int pathSum(TreeNode* root, int targetSum) {
if(root==NULL){
return 0;
}
unordered_map<long long,int>m;
stack<TreeNode*>s;
TreeNode*t=root;
TreeNode*last=NULL;
long long sum=t->val;
int count=0;
if(sum==targetSum){
count++;
}
m.insert({sum,1});
s.push(t);
if(root->val!=0){
m.insert({0,1});
}
else{
m[0]++;
}
while(!s.empty()){
if(t->left!=NULL&&(last==NULL||(last!=t->left&&last!=t->right))){
t=t->left;
sum+=t->val;
if(m.count(sum-targetSum)!=0){
count+=m[sum-targetSum];
}
if(m.count(sum)==0){
m.insert({sum,1});
}
else{
m[sum]++;
}
s.push(t);
continue;
}
if(t->right!=NULL&&last!=t->right){
t=t->right;
sum+=t->val;
if(m.count(sum-targetSum)!=0){
count+=m[sum-targetSum];
}
if(m.count(sum)==0){
m.insert({sum,1});
}
else{
m[sum]++;
}
s.push(t);
continue;
}
last=t;
m[sum]--;
sum-=t->val;
s.pop();
if(s.empty()){
break;
}
t=s.top();
}
return count;
}
};
代码中m[0]=1的意义在于,能够正确记录从根节点开始,路径之和为targetSum的路径。case如下所示(targetSum = 22),如果没有该初始化操作,则5 -> 4 -> 11 -> 2和5 -> 8 -> 4 -> 5这两条路径就不会被正确记录下来。
时间复杂度: O ( n ) O(n) O(n),遍历一次树的时间
空间复杂度: O ( n ) O(n) O(n),最坏情况下树为一个单向链表,这时递归深度为树的节点个数
前缀和初始化很重要
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。
二叉树递归解决问题,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root) {
return nullptr;
}
if (root->val > key) {
root->left = deleteNode(root->left, key);
return root;
}
if (root->val < key) {
root->right = deleteNode(root->right, key);
return root;
}
if (!root->left && !root->right) {
return nullptr;
}
if (!root->left) {
return root->right;
}
if (!root->right) {
return root->left;
}
TreeNode* succ = root->right;
while (succ->left) {
succ = succ->left;
}
root->right = deleteNode(root->right, succ->val);
succ->left = root->left;
succ->right = root->right;
return succ;
}
};
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
迭代实现上述过程,示例代码如下所示:
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
auto cur = root;
TreeNode* cur_p = nullptr;
while (cur && cur->val != key) {
cur_p = cur;
if (cur->val < key) {
cur = cur->right;
} else {
cur = cur->left;
}
}
// 处理待删除节点
if (!cur) {
return root;
}
if (!cur->left && !cur->right) {
cur = nullptr;
} else if (!cur->left) {
cur = cur->right;
} else if (!cur->right) {
cur = cur->left;
} else {
auto cur_succ = cur->right;
auto succ_p = cur;
while (cur_succ->left) {
succ_p = cur_succ;
cur_succ = cur_succ->left;
}
if (succ_p->val == cur->val) {
succ_p->right = cur_succ->right;
} else {
succ_p->left = cur_succ->right;
}
cur_succ->left = cur->left;
cur_succ->right = cur->right;
cur = cur_succ;
}
// 恢复待删除节点的上游连接情况
if (!cur_p) { // 待删除的节点就是root,或者根本就没有
return cur;
} else {
if (cur_p->left && cur_p->left->val == key) {
cur_p->left = cur;
} else {
cur_p->right = cur;
}
return root;
}
}
};
时间: O ( n ) O(n) O(n)
空间: O ( 1 ) O(1) O(1),无需借助额外的递归复杂度
找到待删除节点的下一个节点,并且能够复原删除后的现场
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
示例 2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
后序遍历,先遍历左子树,再遍历右子树,最左下方的节点肯定被优先遍历到,示例代码如下所示:
class Solution {
public:
void dfs(TreeNode* root, int height, int& curVal, int& curHeight) {
if (!root) {
return;
}
++height;
dfs(root->left, height, curVal, curHeight);
dfs(root->right, height, curVal, curHeight);
if (height > curHeight) {
curHeight = height;
curVal = root->val;
}
}
int findBottomLeftValue(TreeNode* root) {
int curVal = 0, curHeight = 0;
dfs(root, 0, curVal, curHeight);
return curVal;
}
};
时间: O ( n ) O(n) O(n)
空间: O ( n ) O(n) O(n)
迭代法层次遍历,队列先入队右子节点,再入队左子节点,这样左子节点能够最后被遍历到,示例代码如下所示:
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
int ret = 0;
while (!q.empty()) {
auto p = q.front();
q.pop();
if (p->right) {
q.push(p->right);
}
if (p->left) {
q.push(p->left);
}
ret = p->val;
}
return ret;
}
};
时间: O ( n ) O(n) O(n)
空间: O ( n ) O(n) O(n)
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
示例 1:
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
示例 2:
输入:root = [0,null,1]
输出:[1,null,1]
示例 3:
输入:root = [1,0,2]
输出:[3,3,2]
示例 4:
输入:root = [3,2,4,1]
输出:[7,9,4,10]
反序二叉树中序遍历解决问题,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* convertBST(TreeNode* root) {
if (root) {
convertBST(root->right);
sum += root->val;
root->val = sum;
convertBST(root->left);
}
return root;
}
private:
int sum = 0;
};
// 递归 反向中序遍历
class Solution {
public:
TreeNode* convertBST(TreeNode* root) {
int sum = 0;
stack<TreeNode*> stk;
auto cur = root;
while (!stk.empty() || cur) {
while (cur) {
stk.push(cur);
cur = cur->right;
}
if (!stk.empty()) {
auto node = stk.top();
stk.pop();
sum += node->val;
node->val = sum;
cur = node->left;
}
}
return root;
}
};
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
给定二叉树
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。
本题题解为最长路径,而不是路径上的最多节点数。本题思路和【124. 二叉树中的最大路径和】几乎一致。示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int diameterOfBinaryTree(TreeNode* root) {
dfs(root);
// 目标是边的最大值,即最大节点数 - 1
return ans - 1;
}
int dfs(TreeNode* root) {
if (!root) {
return 0;
}
int max_left = dfs(root->left);
int max_right = dfs(root->right);
ans = max(ans, max_left + max_right + 1);
return 1 + max(max_left, max_right);
}
private:
int ans = 1;
};
// 写法2
class Solution {
public:
int diameterOfBinaryTree(TreeNode* root) {
dfs(root);
return ans;
}
// dfs返回的是以root为根节点,到叶子节点的最大边数 + 1
int dfs(TreeNode* root) {
if (!root) {
return 0;
}
int max_left = dfs(root->left);
int max_right = dfs(root->right);
ans = max(ans, max_left + max_right);
return 1 + max(max_left, max_right);
}
private:
int ans = 1;
};
注意树相关的方案只能记录节点的信息,并不能记录边的信息,因而需要将节点的信息转换为边的信息。上述代码中ans记录的是最大路径中节点的个数,边的个数为ans-1。而且需要记住的是,dfs函数返回结果为以root为终点的最长路径长度。
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
迭代法解决问题,示例代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
// 时间复杂度为O(n),空间复杂度为O(n),且比递归法的辅助空间开的更多
class Solution {
public:
int diameterOfBinaryTree(TreeNode* root) {
if (!root) {
return 0;
}
unordered_map<TreeNode*, int> m;
int res = -1;
// 给map赋默认值非常重要,这决定了后续遍历会不会死循环
m[nullptr] = 0;
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
auto node = stk.top();
stk.pop();
if (m.count(node->left) && m.count(node->right)) {
m[node] = max(m[node->left], m[node->right]) + 1;
res = max(res, m[node->left] + m[node->right]);
} else {
// 保存现场很重要
stk.push(node);
if (node->right) {
stk.push(node->right);
}
if (node->left) {
stk.push(node->left);
}
}
}
return res;
}
};
递归返回值的含义是以root为根节点的一条路径,对应的路径最大长度。
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
示例 1:
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
这个题和<合并两个有序数组>思路类似,深度优先遍历解决问题。示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1) {
return root2;
}
if (!root2) {
return root1;
}
TreeNode* root = new TreeNode(root1->val + root2->val);
root->left = mergeTrees(root1->left, root2->left);
root->right = mergeTrees(root1->right, root2->right);
return root;
}
};
// 迭代形式
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
if (t1 == nullptr) {
return t2;
}
if (t2 == nullptr) {
return t1;
}
auto merged = new TreeNode(t1->val + t2->val);
auto q = queue<TreeNode*>();
auto queue1 = queue<TreeNode*>();
auto queue2 = queue<TreeNode*>();
q.push(merged);
queue1.push(t1);
queue2.push(t2);
while (!queue1.empty() && !queue2.empty()) {
auto node = q.front(), node1 = queue1.front(), node2 = queue2.front();
q.pop();
queue1.pop();
queue2.pop();
auto left1 = node1->left, left2 = node2->left, right1 = node1->right, right2 = node2->right;
if (left1 != nullptr || left2 != nullptr) {
if (left1 != nullptr && left2 != nullptr) {
auto left = new TreeNode(left1->val + left2->val);
node->left = left;
q.push(left);
queue1.push(left1);
queue2.push(left2);
} else if (left1 != nullptr) {
node->left = left1;
} else if (left2 != nullptr) {
node->left = left2;
}
}
if (right1 != nullptr || right2 != nullptr) {
if (right1 != nullptr && right2 != nullptr) {
auto right = new TreeNode(right1->val + right2->val);
node->right = right;
q.push(right);
queue1.push(right1);
queue2.push(right2);
} else if (right1 != nullptr) {
node->right = right1;
} else {
node->right = right2;
}
}
}
return merged;
}
};
时间复杂度: O ( m i n ( m , n ) ) O(min(m, n)) O(min(m,n)),m和n代表两棵树的节点数
空间复杂度: O ( m i n ( m , n ) ) O(min(m, n)) O(min(m,n))
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
二分遍历解决问题,示例代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
auto cur = root;
while (cur && cur->val != val) {
if (cur->val < val) {
cur = cur->right;
} else {
cur = cur->left;
}
}
return cur;
}
};
// 递归形式
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (!root) {
return nullptr;
}
if (root->val == val) {
return root;
}
return searchBST(val < root->val ? root->left : root->right, val);
}
};
时间: O ( l o g n ) O(logn) O(logn)
空间: O ( 1 ) O(1) O(1)
二叉搜索树的左小右大的本质,以及二分法与二叉树本质的完美契合。
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
直接找到合适的空位,插入节点即可,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root) {
return new TreeNode(val);
}
auto p = root;
while (p) {
if (p->val < val) {
if (p->right == nullptr) {
p->right = new TreeNode(val);
break;
} else {
p = p->right;
}
} else {
if (p->left == nullptr) {
p->left = new TreeNode(val);
break;
} else {
p = p->left;
}
}
}
return root;
}
};
// 递归方法
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root) {
return new TreeNode(val);
}
if (root->val > val) {
root->left = insertIntoBST(root->left, val);
} else {
root->right = insertIntoBST(root->right, val);
}
return root;
}
};
时间: O ( n ) O(n) O(n)
空间: O ( 1 ) O(1) O(1)
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
中序遍历过程中记录最小差值,示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int minDiffInBST(TreeNode* root) {
int res = INT_MAX;
stack<TreeNode*> stk;
bool flag = false;
int last = 0;
while (root || !stk.empty()) {
while (root) {
stk.push(root);
root = root->left;
}
if (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
if (flag && node->val - last < res) {
res = node->val - last;
}
root = node->right;
last = node->val;
flag = true;
}
}
return res;
}
};
// 递归形式
class Solution {
public:
void dfs(TreeNode* root, int& pre, int& ans) {
if (root == nullptr) {
return;
}
dfs(root->left, pre, ans);
if (pre != -1) {
ans = min(ans, root->val - pre);
}
pre = root->val;
dfs(root->right, pre, ans);
}
int minDiffInBST(TreeNode* root) {
int ans = INT_MAX, pre = -1;
dfs(root, pre, ans);
return ans;
}
};
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n),有额外的栈空间
请实现一个函数来判断整数数组 postorder 是否为二叉搜索树的后序遍历结果。
递归验证,示例代码如下:
class Solution {
public:
bool verifyTreeOrder(vector<int>& postorder) {
return dfs(postorder, 0, postorder.size() - 1);
}
bool dfs(vector<int>& postorder, int i, int j) {
if (i >= j) {
return true;
}
int p = i;
while (postorder[p] < postorder[j]) {
++p;
}
int m = p;
while (postorder[p] > postorder[j]) {
++p;
}
return p == j && dfs(postorder, i, m - 1) && dfs(postorder, m, j - 1);
}
};
示例代码和注解如下所示,题解见 这里。
public boolean verifyPostorder(int[] postorder) {
Stack<Integer> stack = new Stack<>();
int parent = Integer.MAX_VALUE;
//注意for循环是倒叙遍历的
for (int i = postorder.length - 1; i >= 0; i--) {
int cur = postorder[i];
//当如果前节点小于栈顶元素,说明栈顶元素和当前值构成了倒叙,
//说明当前节点是前面某个节点的左子节点,我们要找到他的父节点
while (!stack.isEmpty() && stack.peek() > cur)
parent = stack.pop();
//只要遇到了某一个左子节点,才会执行上面的代码,才会更
//新parent的值,否则parent就是一个非常大的值,也就
//是说如果一直没有遇到左子节点,那么右子节点可以非常大
if (cur > parent)
return false;
//入栈
stack.add(cur);
}
return true;
}
给定一棵二叉树和其中的一个结点,如何找出中序遍历顺序的下一个结点?树中的结点除了有两个分别指向左右子结点的指针以外,还有一个指向父结点的指针。
最复杂的情况在于如果一个节点node, 它是其父节点的右节点而且没有右子树,那么就向父节点回溯,直到找到一个节点node, 如果它是它的父节点的左子节点,那么node的父节点就是我们要找到的node的下一个节点。示例代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode *parent;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* nextNode(TreeNode* root) {
if (!root) {
return nullptr;
}
TreeNode* next_node = root->right;
if (next_node) {
while (next_node->left) {
next_node = next_node->left;
}
} else {
auto parent_node = root->parent;
while (parent_node && root == parent_node->right) { // 边界值
root = parent_node;
parent_node = parent_node->parent;
}
next_node = parent_node;
}
return next_node;
}
};
时间复杂度: O ( n ) O(n) O(n),遍历树的时间复杂度
空间复杂度: O ( 1 ) O(1) O(1),没有额外的辅助空间
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
示例 1:
输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:
0 <= 节点个数 <= 10000
先看以B为根节点的树是否是以A为根节点的树的子树,如果是的话直接返回,如果不是则看以B为根节点的树是否是以A->left为根节点的树的子树,如果不是的话则再看下以B为根节点的树是否是以A->right为根节点的树的子树,通过上述递归的方法就能找到最终的结果。
示例代码如下所示:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool is_sub(TreeNode* A, TreeNode* B) {
if (!B) {
return true;
}
if (!A) {
return false;
}
if (A->val != B->val) {
return false;
}
return is_sub(A->left, B->left) && is_sub(A->right, B->right);
}
bool isSubStructure(TreeNode* A, TreeNode* B) {
if (A && B) {
if (is_sub(A, B)) {
return true;
}
return isSubStructure(A->left, B) || isSubStructure(A->right, B);
}
return false;
}
};
时间复杂度: O ( M N ) O(MN) O(MN),M和N代表A和B节点个数,遍历A的某个节点时,总共时间复杂度为B的节点数
空间复杂度: O ( M ) O(M) O(M),A的节点个数
中序遍历过程中保存pre和head解决问题,在遍历子树的根节点时,需要让pre->right指向根节点,根节点的left指向pre,示例代码如下所示:
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node() {}
Node(int _val) {
val = _val;
left = NULL;
right = NULL;
}
Node(int _val, Node* _left, Node* _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
// 递归版本
class Solution {
public:
Node* treeToDoublyList(Node* root) {
if (!root) {
return nullptr;
}
core(root);
head->left = pre;
pre->right = head;
return head;
}
void core(Node* cur) {
if (!cur) {
return;
}
core(cur->left);
if (pre) {
pre->right = cur;
} else {
head = cur;
}
cur->left = pre;
pre = cur;
core(cur->right);
}
private:
Node* pre;
Node* head;
};
// 迭代版本
class Solution {
public:
Node* treeToDoublyList(Node* root) {
if (!root) {
return nullptr;
}
stack<Node*> stk;
Node* pre = nullptr;
Node* head = nullptr;
while (root || !stk.empty()) {
while (root) {
stk.push(root);
root = root->left;
}
if (!stk.empty()) {
auto cur = stk.top();
stk.pop();
if (pre) {
pre->right = cur;
} else {
head = cur;
}
cur->left = pre;
pre = cur;
root = cur->right;
}
}
head->left = pre;
pre->right = head;
return head;
}
};
时间: O ( n ) O(n) O(n)
空间: O ( n ) O(n) O(n),最坏情况下树变成链表,这时栈的深度为n。
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
输入:root = [3,9,20,null,null,15,7]
输出:true
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int depth(TreeNode* root) {
if (!root) {
return 0;
}
int left_depth = depth(root->left);
int right_depth = depth(root->right);
return 1 + max(left_depth, right_depth);
}
bool isBalanced(TreeNode* root) {
if (!root) {
return true;
}
int left_depth = depth(root->left);
int right_depth = depth(root->right);
if (abs(left_depth - right_depth) >= 2) {
return false;
}
return isBalanced(root->left) && isBalanced(root->right);
}
};
时间复杂度: O ( n 2 ) O(n^2) O(n2),最坏情况为树是一条链表,整体时间复杂度为 O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n),递归空间
class Solution {
public:
int depth(TreeNode* root) {
if (!root) {
return true;
}
int left_depth = depth(root->left);
int right_depth = depth(root->right);
if (left_depth == -1 || right_depth == -1 || abs(left_depth - right_depth) >= 2) {
return -1;
}
return 1 + max(left_depth, right_depth);
}
bool isBalanced(TreeNode* root) {
return depth(root) >= 0;
}
};
时间复杂度: O ( n ) O(n) O(n),不会重复遍历
空间复杂度: O ( n ) O(n) O(n)