节点定义如下:
/**
* 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) {}
* };
*/
226.翻转二叉树
题目:给你一棵二叉树的根节点root
,翻转这棵二叉树,并返回其根节点。
思路:简单递归
通过代码:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(!root)
return root;
invertTree(root -> left);
invertTree(root -> right);
TreeNode *tmp = root -> left;
root -> left = root -> right;
root -> right = tmp;
return root;
}
};
101. 对称二叉树
题目:给你一个二叉树的根节点root
, 检查它是否轴对称。
思路:递归和迭代都可以,关键是要认清比较对象。左孩子的左孩子和右孩子的右孩子比,左孩子的右孩子和右孩子的左孩子比。
通过代码:
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(!root)
return true;
queue<TreeNode*> q;
q.push(root -> left);
q.push(root -> right);
while(!q.empty())
{
TreeNode *left = q.front(); q.pop();
TreeNode *right = q.front(); q.pop();
if(!left && !right)
continue;
if(left && !right || !left && right || left -> val != right -> val)
return false;
q.push(left -> left);
q.push(right -> right);
q.push(left -> right);
q.push(right -> left);
}
return true;
}
};
104.二叉树的最大深度
题目:给定一个二叉树root
,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
思路一:递归,深度为左右子树中较大的深度+1。
通过代码:
class Solution {
public:
int maxDepth(TreeNode* root) {
if(!root)
return 0;
return max(maxDepth(root -> left), maxDepth(root -> right)) + 1;
}
};
思路二:层序遍历的时候记录深度。
111.二叉树的最小深度
题目:给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
思路:层序遍历(也就是宽搜)的时候记录深度,遇到第一个叶子节点的时候就是最小深度。
通过代码:
class Solution {
public:
int minDepth(TreeNode* root) {
int depth = 0;
queue<TreeNode*> q;
if(root)
q.push(root);
while(!q.empty())
{
depth++;
int size = q.size();
for(int i = 0; i < size; i++)
{
TreeNode *node = q.front();
q.pop();
if(!node -> left && !node -> right)
return depth;
if(node -> left) q.push(node -> left);
if(node -> right) q.push(node -> right);
}
}
return depth;
}
};
222.完全二叉树的节点个数
题目:给你一棵完全二叉树的根节点 root
,求出该树的节点个数。
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第h
层,则该层包含1~ 2^h
个节点。
思路:直接递归遍历一个个数是可以的,但是太慢,没有用到完全二叉树的性质。可以优化一下。观察能发现,完全二叉树的子树是包含满二叉树的。而满二叉树的节点数根据层数可以用公式直接算出来:$ 2^{层数}-1 $。在一个完全二叉树里判断满二叉树,只要最左边和最右边向下迭代得到的层数相等就是满二叉树。
通过代码:
class Solution {
public:
int countNodes(TreeNode* root) {
if(!root)
return 0;
// 以下是优化部分,没有这部分也能过
int depth_left = 0, depth_right = 0;
TreeNode *left = root, *right = root;
while(left)
{
depth_left++;
left = left -> left;
}
while(right)
{
depth_right++;
right = right -> right;
}
if(depth_left == depth_right)
return pow(2, depth_left) - 1;
// 以上是优化部分
return countNodes(root -> left) + countNodes(root -> right) + 1;
}
};
110.平衡二叉树
题目:给定一个二叉树,判断它是否是 平衡二叉树。
思路:递归,左右子树是平衡的,并且左右子树高度差不超过1。所以需要一个函数来求高度。104.二叉树的最大深度
这道题已经解决了,用一个小递归求得高度,再在判断平衡时递归一下即可。但是这样写求高度的时候遍历了一遍树,判断平衡的时候又遍历了一遍,重复了。所以完全可以合在一起,在求高度的时候就可以判断是否平衡了。不平衡直接返回-1,平衡的时候再正常求高度。
通过代码:
class Solution {
public:
int height(TreeNode *root){
if(!root)
return 0;
int left_height = height(root -> left);
int right_height = height(root -> right);
if(left_height == -1 || right_height == -1 || abs(left_height - right_height) > 1)
return -1;
return max(left_height, right_height) + 1;
}
bool isBalanced(TreeNode* root) {
return height(root) >= 0;
}
};
257. 二叉树的所有路径
题目:给你一个二叉树的根节点root
,按任意顺序,返回所有从根节点到叶子节点的路径。叶子节点是指没有子节点的节点。
思路一:递归+回溯,path记录路径,res保存结果。如果左右孩子都为空,说明到叶子节点了,res保存一下path后返回。回溯体现在,path没有加引用,是按值传递。return回去之后,path还是上一层的值,可以继续换右孩子进行遍历。
通过代码:
class Solution {
public:
void traverse(TreeNode *root, string path, vector<string> &res){
path += to_string(root -> val);
if(!root -> left && !root -> right)
{
res.push_back(path);
return;
}
if(root -> left)
traverse(root -> left, path + "->", res);
if(root -> right)
traverse(root -> right, path + "->", res);
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> res;
string path;
traverse(root, path, res);
return res;
}
};
思路二:宽搜,层序遍历。
通过代码:
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> res;
queue<TreeNode*> q_tree;
queue<string> q_path;
if(root)
{
q_tree.push(root);
q_path.push(to_string(root -> val));
}
while(!q_tree.empty())
{
TreeNode *node = q_tree.front(); q_tree.pop();
string path = q_path.front(); q_path.pop();
if(!node -> left && !node -> right)
res.push_back(path);
if(node -> left)
{
q_tree.push(node -> left);
q_path.push(path + "->" + to_string(node -> left -> val));
}
if(node -> right)
{
q_tree.push(node -> right);
q_path.push(path + "->" + to_string(node -> right -> val));
}
}
return res;
}
};
404.左叶子之和
题目:给定二叉树的根节点root
,返回所有左叶子之和。
思路:深搜,类似前序遍历
通过代码:
class Solution {
public:
void traverse(TreeNode *root, int &s){
if(!root)
return;
if(root -> left && !root -> left ->left && !root -> left -> right)
s += root -> left -> val;
traverse(root -> left, s);
traverse(root -> right, s);
}
int sumOfLeftLeaves(TreeNode* root) {
int s = 0;
traverse(root, s);
return s;
}
};
513.找树左下角的值
题目:给定一个二叉树的根节点root
,请找出该二叉树的最底层 最左边节点的值。
假设二叉树中至少有一个节点。
思路一:用两个变量保存当前找到的值和深度。其实找最深的节点就行了,不管是哪种遍历,左都在右前面遍历到,先遇到的一定是左,保存下来的也就是最左边的。
通过代码:
class Solution {
public:
int val, depth = -1;
void traverse(TreeNode *root, int d){
if(!root)
return;
if(!root -> left && !root -> right)
{
if(d > depth)
{
val = root -> val;
depth = d;
}
}
traverse(root -> left, d + 1);
traverse(root -> right, d + 1);
}
int findBottomLeftValue(TreeNode* root) {
traverse(root, 1);
return val;
}
};
思路二:宽搜,层序遍历,很简单。
112. 路径总和
题目:给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
思路:递归,首先得找到任务分解的思路。从根节点开始找和等于sum,可以分解位从根节点的两个孩子开始找和等于sum - root->val
。最后确定一下递归边界,即到叶节点的时候只要判断sum和自己的val是否相等即可。
通过代码:
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if(!root)
return false;
if(!root -> left && !root -> right && root -> val == targetSum)
return true;
return hasPathSum(root -> left, targetSum - root ->val) || hasPathSum(root -> right, targetSum - root -> val);
}
};
106.从中序与后序遍历序列构造二叉树
题目:给定两个整数数组inorder
和postorder
,其中inorder
是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请你构造并返回这颗二叉树 。
思路:通过后序数组的最后一个可以找到根节点。在前序数组中遍历找到根节点的位置,根节点左边为左子树,根节点右边为右子树。根据切割出的左右子数组的长度,在后序数组里同样能切出左右子树。递归找到左右子树的根节点即可。
通过代码:
class Solution {
public:
TreeNode *traverse(vector<int> &inorder, int in_begin, int in_end, vector<int> &postorder, int post_begin, int post_end){
if(post_end - post_begin == 0)
return nullptr;
int root_value = postorder[post_end - 1];
TreeNode *root = new TreeNode(root_value);
if(post_end - post_begin == 1)
return root;
int i;
for(i = in_begin; i < in_end; i++)
if(inorder[i] == root_value)
break;
int len_left = i - in_begin;
int post_left_end = post_begin + len_left;
int post_right_begin = post_left_end;
int post_right_end = post_end - 1;
root -> left = traverse(inorder, in_begin, i, postorder, post_begin, post_left_end);
root -> right = traverse(inorder, i + 1, in_end, postorder, post_right_begin, post_right_end);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(inorder.size() == 0 || postorder.size() == 0)
return nullptr;
return traverse(inorder, 0, inorder.size(), postorder, 0, postorder.size());
}
};
654.最大二叉树
题目:给定一个不重复的整数数组nums
。 最大二叉树可以用下面的算法从nums
递归地构建:
- 创建一个根节点,其值为
nums
中的最大值。 - 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回nums
构建的最大二叉树 。
思路:有点类似上面那道题,不过简单一点。根据题目意思构建递归过程即可。
通过代码:
class Solution {
public:
TreeNode *dfs(vector<int> &nums, int begin, int end){
if(end - begin == 0)
return nullptr;
int idx = begin, max_val = nums[begin];
for(int i = begin + 1; i < end; i++)
{
if(nums[i] > nums[idx])
{
idx = i;
max_val = nums[i];
}
}
TreeNode *root = new TreeNode(max_val);
root -> left = dfs(nums, begin, idx);
root -> right = dfs(nums, idx + 1, end);
return root;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
if(nums.size() == 0)
return nullptr;
return dfs(nums, 0, nums.size());
}
};
617.合并二叉树
题目:给你两棵二叉树: root1
和 root2
。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
思路:递归同时遍历两个树,当两个根节点都不为空时,借用root1为新的节点,把root2的值加进去。然后递归得到合并后的左(右)子树根节点并赋值。
通过代码:
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(!root1)
return root2;
if(!root2)
return root1;
root1 -> val += root2 -> val;
root1 -> left = mergeTrees(root1 -> left, root2 -> left);
root1 -> right = mergeTrees(root1 -> right, root2 -> right);
return root1;
}
};
700.二叉搜索树中的搜索
题目:给定二叉搜索树(BST)的根节点 root
和一个整数值 val
。
你需要在 BST 中找到节点值等于 val
的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null
。
思路:迭代即可
通过代码:
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
TreeNode *node = root;
while(node)
{
if(node -> val == val)
return node;
else if(node -> val < val)
node = node -> right;
else
node = node -> left;
}
return nullptr;
}
};
98.验证二叉搜索树
题目:给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
思路:中序遍历二叉搜索树应该是递增的,所以中序遍历的时候当前节点的值大于前一个节点即可。
通过代码:
class Solution {
public:
TreeNode *pre;
bool isValidBST(TreeNode* root) {
if(!root)
return true;
bool left = isValidBST(root -> left);
if(pre && pre -> val >= root -> val)
return false;
pre = root;
bool right = isValidBST(root -> right);
return left && right;
}
};
530.二叉搜索树的最小绝对差
题目:给你一个二叉搜索树的根节点 root
,返回树中任意两不同节点值之间的最小差值 。差值是一个正数,其数值等于两值之差的绝对值。
思路:利用二叉搜索树的性值,中序遍历二叉搜索树是一个递增序列,因此只要比较相邻的节点即可。类似上面那道题,用一个pre记录前一节点即可。
通过代码:
class Solution {
public:
TreeNode *pre = nullptr;
int res = INT_MAX;
void traverse(TreeNode *root){
if(!root)
return;
traverse(root -> left);
if(pre)
res = min(res, abs(root -> val - pre -> val));
pre = root;
traverse(root -> right);
}
int getMinimumDifference(TreeNode* root) {
traverse(root);
return res;
}
};
501.二叉搜索树中的众数
题目:给你一个含重复值的二叉搜索树(BST)的根节点 root
,找出并返回 BST 中的所有众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按任意顺序返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
思路:如果这不是二叉搜索树,就需要遍历所有节点统计次数并排序。但这是二叉搜索树,可以利用其中序遍历递增的特性。中序遍历时,值相同的节点一定连在一起,于是可以统计相同节点的次数并记录最大次数,若有和最大次数相同的,加入结果集;若比最大次数还大,更新最大次数、清空结果集、将新节点加入结果集。
通过代码:
class Solution {
public:
TreeNode *pre = nullptr;
int count = 0, maxcount = 0;
vector<int> res;
void traverse(TreeNode *root){
if(!root)
return;
traverse(root -> left);
if(!pre)
count = 1;
else if(root -> val == pre -> val)
count++;
else
count = 1;
pre = root;
if(count == maxcount)
res.push_back(root -> val);
else if(count > maxcount)
{
maxcount = count;
res.clear();
res.push_back(root -> val);
}
traverse(root -> right);
}
vector<int> findMode(TreeNode* root) {
traverse(root);
return res;
}
};
236. 二叉树的最近公共祖先
题目:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路:从下往上找,符合后序遍历。如果当前根节点为空或者正好等于指定节点,直接返回当前根节点。因为当前节点等于指定节点的话,没有必要再往下递归下去了,即便子树中有另一个指定节点,要返回的公共祖先还是当前节点。如果子树中没有另一个指定节点,就应该把当前节点传递上去。递归调用找到左右子树的公共祖先,如果都不为空,说明两个目标节点就在这两个子树中,最低公共祖先就是当前根节点;如果只有一个不为空,说明此时要么公共祖先已经找到了,该把答案返回去了;要么还有一个目标节点在上层,要返回上去汇合。
通过代码:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root || root == p || root == q)
return root;
TreeNode *left = lowestCommonAncestor(root -> left, p, q);
TreeNode *right = lowestCommonAncestor(root -> right, p, q);
if(left && right)
return root;
else if(!left)
return right;
return left;
}
};
235. 二叉搜索树的最近公共祖先
题目:给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路:本题和上一题的区别就在于二叉搜索树。结合二叉搜索树的性质,最近公共祖先的大小一定是位于两个节点之间的。因此,从上往下遍历即可。
通过代码:
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root -> val > p -> val && root -> val > q -> val)
return lowestCommonAncestor(root -> left, p, q);
else if(root -> val < p -> val && root -> val < q -> val)
return lowestCommonAncestor(root -> right, p, q);
else
return root;
}
};
701.二叉搜索树中的插入操作
题目:给定二叉搜索树(BST)的根节点 root
和要插入树中的值 value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
思路:简单递归。注意递归要返回根节点,回到上一层用left或right接住。
通过代码:
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if(!root)
{
root = new TreeNode(val);
return root;
}
if(val < root -> val)
root -> left = insertIntoBST(root -> left, val);
if(val > root -> val)
root -> right = insertIntoBST(root -> right, val);
return root;
}
};
450.删除二叉搜索树中的节点
题目:给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
思路:找目标节点很容易,关键是删除节点要保持二叉搜索树的性质。找到目标节点后要考虑几种情况:
- 目标节点是叶节点,此时最容易,直接删除即可,不会影响什么;
- 目标节点左或右子树有一个为空,删除目标节点后,需要把不为空右或左子树提上来,返回的时候返回对应的节点地址即可。
- 目标节点左右子树都不为空,此时比较麻烦。由于目标节点比左子树大,比右子树小。所以删除之后需要把左子树最大的或右子树最小的提上来。这里选右子树最小的。右子树最小的只要一直往左下迭代就能找到。找到之后交换目标节点和右子树最小的节点的值。这样目标节点就到右子树去了,然后以右子树为根节点递归删除key即可。
通过代码:
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if(!root)
return root;
if(root -> val == key)
{
if(!root -> left && !root -> right)
{
delete root;
return nullptr;
}
else if(!root -> left)
{
TreeNode *node = root -> right;
delete root;
return node;
}
else if(!root -> right)
{
TreeNode *node = root -> left;
delete root;
return node;
}
else
{
TreeNode *node = root -> right;
while(node -> left)
node = node -> left;
swap(root -> val, node -> val);
root -> right = deleteNode(root -> right, key);
return root;
}
}
if(key < root -> val)
root -> left = deleteNode(root -> left, key);
if(key > root -> val)
root -> right = deleteNode(root -> right, key);
return root;
}
};
669. 修剪二叉搜索树
题目:给你二叉搜索树的根节点 root
,同时给定最小边界low
和最大边界 high
。通过修剪二叉搜索树,使得所有节点的值在[low, high]
中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
思路:对于当前访问的结点,如果结点为空结点,直接返回空结点;如果结点的值小于 low,那么说明该结点及它的左子树都不符合要求,我们返回对它的右结点进行修剪后的结果;如果结点的值大于 high,那么说明该结点及它的右子树都不符合要求,我们返回对它的左子树进行修剪后的结果;如果结点的值位于区间 [low,high],我们将结点的左结点设为对它的左子树修剪后的结果,右结点设为对它的右子树进行修剪后的结果。
通过代码:
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if(!root)
return nullptr;
if(root -> val > high)
return trimBST(root -> left, low, high);
if(root -> val < low)
return trimBST(root -> right, low, high);
root -> left = trimBST(root -> left, low, high);
root -> right = trimBST(root -> right, low, high);
return root;
}
};
108.将有序数组转换为二叉搜索树
题目:给你一个整数数组 nums
,其中元素已经按升序排列,请你将其转换为一棵平衡二叉搜索树。
思路:每次都取中间的值作为根节点即可,简单递归一下。
通过代码:
class Solution {
public:
TreeNode* construct(vector<int> &nums, int low, int high){
if(high < low)
return nullptr;
int mid = (low + high) / 2;
TreeNode *root = new TreeNode(nums[mid]);
root -> left = construct(nums, low, mid - 1);
root -> right = construct(nums, mid + 1, high);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
return construct(nums, 0, nums.size() - 1);
}
};
538.把二叉搜索树转换为累加树
题目:给出二叉搜索树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node
的新值等于原树中大于或等于 node.val
的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
思路:中序遍历二叉搜索树得到的是一个递增的序列,累加树每个节点的新值也就是序列中该节点及其后面的序列求和。所以我们反中序(右中左)遍历的同时记录累加和即可。
通过代码:
class Solution {
public:
int sum = 0;
void traverse(TreeNode *root){
if(!root)
return;
traverse(root -> right);
sum += root -> val;
root -> val = sum;
traverse(root -> left);
}
TreeNode* convertBST(TreeNode* root) {
traverse(root);
return root;
}
};