二叉树的最大深度
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明:叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],相关标签:树 深度优先搜索 广度优先搜索 二叉树
3
/ \
9 20
/ \
15 7
返回它的最大深度3
#pragma once #include<algorithm> #include<queue> //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) { /*by myself(深度优先,递归实现) if (root == nullptr) { //遍历到空结点返回 return 0; } return std::max(maxDepth(root->left), maxDepth(root->right)) + 1; //比较每个递归层root的左子树和右子数深度,返回值为 其中的最大深度再加1(因为root本身也是一层,当前递归层最大深度即 max(maxDepth(root->left), maxDepth(root->right)) + 1) */ //(广度优先,使用了队列) if (root == nullptr) { //如果为空树返回0,否则在if (t->left != nullptr)判断时会非法引用 return 0; } std::queue<TreeNode*>que; //队列的作用是存放当前层所有的结点 int level = 0; //记录二叉树最大深度(层数) que.push(root); //首先将第一层的root节点入队 while (!que.empty()) { //如果队列为空,表示遍历到最深一层,由于最深一层都没有子结点,所以当进入下一层查找时最深一层作为父结点会被出队 int father_nodes = que.size(); //father_nodes表示这一层结点的个数(也是下一层的父节点的个数) while (father_nodes) { //遍历这一层父结点,查找是否存在子节点,存在就入队,每个父结点查找完毕后将其出队,因此队列里都是下一层的子结点(每次遍历队列中的上层父节点,入队其子结点后删除父结点,队列中的这层子结点将作为下一层的父结点继续遍历查找。直到遍历到最深一层,最深一层的结点没有子结点,所以遍历后没有可入队的子结点,并且删除队列里的这层父结点,因此队列最后为空) TreeNode* t = que.front(); if (t->left != nullptr) { que.push(t->left); } if (t->right != nullptr) { que.push(t->right); } que.pop(); father_nodes--; //遍历查找后删除一个父结点,father_nodes-- } level++; //内层while循环结束,表示一层遍历完毕,因此level++ } return level; //返回最后得到的level,即最大深度 } };
验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征:
- 节点的左子树只包含小于当前节点的数。
- 节点的右子树只包含大于当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
输入:2/ \1 3输出: true
输入:5/ \1 4/ \3 6输出: false解释: 输入为: [5,1,4,null,null,3,6]。根节点的值为 5 ,但是其右子节点值为 4 。
相关标签:树 深度优先搜索 二叉搜索树 二叉树
例如:
#pragma #include<iostream> #include<stack> //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 helper(TreeNode* root, long long min, long long max); bool isValidBST(TreeNode* root) { /* (错误理解) 只考虑到子结点和上一层父结点直间的大小关系,而没有考虑到全部(例如,不仅右子结点应大于其父节点的值,而是整个右子树的所有结点(包括其左子结点)都应该大于其父结点的值) * if (root->left != nullptr && root->right != nullptr) { * if (root->val <= root->left->val || root->val >= root->right->val) { * return false; * } * * if (!isValidBST(root->left)) { * return false; * } * if (!isValidBST(root->right)) { * return false; * } * } * return true; */ /*(递归) return helper(root, LONG_MIN, LONG_MAX); */ //(中序遍历,迭代,利用栈) std::stack<TreeNode*>stk; //使用栈来模拟中序遍历的过程 long long min = LONG_MIN; //定义一个最小值(如果满足二叉搜索树再结合中序遍历特点,可知中序遍历后的所以结点是从小到大排列的。所以只要按照中序遍历对所有结点进行比较,如果满足从小到大的次序则是二叉搜索树) while (!stk.empty() || root != nullptr) { //初始时,栈是空的,root!=nullptr(非空树);中序遍历过程中,栈不是空的,root==nullptr或者root!=nullptr(即右结点可能是空也可能不是);遍历结束时,栈是空的,root==nullptr(所有应该为||而不是&&,否则进入不了循环,中间遍历过程也可能突然退出) while (root != nullptr) { //只要left存在就一直找left,直到nullptr stk.push(root); //左要保留在栈中 root = root->left; } root = stk.top(); //当root==nullptr(即left_r->left==nulltr),弹出left_r,root=left_r stk.pop(); if (root->val <= min) { //此时的root(left_r)即 中,将 中 与min比较,如果 中>=min,则说明不是二叉搜索树(不管前序、中序、后序遍历,所有结点都会有作为 中,比如前序遍历第一次遍历到该结点就是 中,中序遍历第二次遍历到该结点就是 中,后续遍历第三次遍历到该结点就是 中。所有所有结点都会成为中别遍历到,三种遍历只是说最终所有结点作为 中 被遍历到的顺序不同而已) return false; } min = root->val; //min被设置为被遍历到的结点值,接着与下一个 中 比较 root = root->right; //找完了左,中,找右(以右作为新的子树root,继续不断找左) } return true; //如果完整中序遍历完,说明该树就是二叉搜索树 } }; bool Solution::helper(TreeNode* root, long long min, long long max) { //自己设计一个递归函数,增加每个结点值范围的参数(min,max) if (root == nullptr) { //遍历到空结点,返回true return true; } if (root->val <= min || root->val >= max) { //该结点不在范围内,即不满足二叉搜索树,返回false return false; } return helper(root->left, min, root->val) && helper(root->right, root->val, max); //对左右子树进行递归,只有在都返回true时才满足二叉搜索树的条件(这里对左子树root的范围为(min, root->val),对左子树root的范围为(root->val, max)。min和max在动态变化,即被覆盖,最终的目的就是要满足,对于左子树而言:左子树上的所有结点都应该小于这个左子树root,虽然存在树里面的子树,不管是外层树还是内层子树,只要该结点在其树上,既要小于外层树root,也要小于内存子树的root) }
对称二叉树
给定一个二叉树,检查它是否是镜像对称的。例如:
二叉树 [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
进阶:
你可以运用递归和迭代两种方法解决这个问题吗?
相关标签:树 深度优先搜索 广度优先搜索 二叉树
示例:
你可以运用递归和迭代两种方法解决这个问题吗?
相关标签:树 深度优先搜索 广度优先搜索 二叉树
#pragma once #include<deque> #include<queue> #include<stack> #include<vector> //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 is_hui_wen(std::deque<TreeNode*> DQ_t); //验证双端队列DQ_t中存储的结点是否构成回文(DQ_t中存储二叉树当前层的结点) bool is_hui_wen(std::vector<TreeNode*> vec); //验证双端队列DQ_t中存储的结点是否构成回文(vec中存储二叉树当前层的结点的值) bool compare(TreeNode* t1, TreeNode* t2); //比较对称位置t1,t2结点是否都存在,如果存在是否值相同 bool check(TreeNode* t1, TreeNode* t2); //官方代码简化版 bool isSymmetric(TreeNode* root) { /*by myself(层序遍历,回文判断,有点暴力味) std::deque<TreeNode*>DQ; std::deque<TreeNode*>DQ_t; TreeNode* t = new TreeNode(-1); //假设所有结点值都>0,在这里t代指一个空结点,起到占位作用(埋了个内存泄露的伏笔) DQ.push_back(root); DQ_t.push_back(root); while (!DQ.empty()) { //中序遍历 if (!is_hui_wen(DQ_t)) { //如果当前层的结点按从左到右顺序不构成回文,则直接返回false return false; } DQ_t.clear(); //清空双端队列DQ_t,即清空上一层结点,准备存入当前层结点 int level_nodes = DQ.size(); //双端队列DQ_t中存储着上一层结点,寻找当前层结点是根据上一层结点进行 while (level_nodes) { //根据上一层记录的结点,寻找下一层结点 root = DQ.front(); if (root->left != nullptr) { //以上一层结点为root,如果存在左右结点,则插入到DQ和DQ_t中(每查找完DQ中存储的当前root结点,如果存在其左右则插入,插入完删除该root,继续查找DQ中下一个root) DQ.push_back(root->left); DQ_t.push_back(root->left); } else { DQ_t.push_back(t); //如果root不存在left或者right,对于DQ是不插入的,但是对于DQ_t需要插入一个t } if (root->right != nullptr) { DQ.push_back(root->right); DQ_t.push_back(root->right); } else { DQ_t.push_back(t); } DQ.pop_front(); level_nodes--; //每查找完上一层一个结点,level_nodes--,当level_nodes==0,表示上一层结点查找遍历完毕 } } return true; */ /*by myself(层序遍历,回文判断,简单优化:用queue和vector代替deque) std::queue<TreeNode*>Q; std::vector<TreeNode*>vec; Q.push(root); vec.push_back(root); while (!Q.empty()) { //中序遍历 if (!is_hui_wen(vec)) { //如果当前层的结点按从左到右顺序不构成回文,则直接返回false return false; } vec.clear(); //清空vec,即清空上一层结点,准备存入当前层结点 int level_nodes = Q.size(); //队列Q中存储着上一层结点,寻找当前层结点是根据上一层结点进行 while (level_nodes) { //根据上一层记录的结点,寻找下一层结点 root = Q.front(); if (root->left != nullptr) { //以上一层结点为root,如果存在左右结点,则插入到Q和vec中(每查找完DQ中存储的当前root结点,如果存在其左右则插入,插入完删除该root,继续查找Q中下一个root) Q.push(root->left); vec.push_back(root->left); } else { vec.push_back(nullptr); //如果root不存在left或者right,对于Q是不插入的,但是对于vec需要插入一个nullptr(nullptr起到占位作用) } if (root->right != nullptr) { Q.push(root->right); vec.push_back(root->right); } else { vec.push_back(nullptr); } Q.pop(); level_nodes--; //每查找完上一层一个结点,level_nodes--,当level_nodes==0,表示上一层结点查找遍历完毕 } } return true; */ /*by myself(递归) return compare(root->left, root->right); */ /*(递归,代码排放简明,逻辑性更强) return check(root->left, root->right); */ /*by myself(迭代,栈实现) std::stack<TreeNode*>stk; stk.push(root); stk.push(root); TreeNode* t1 =root->left; TreeNode* t2 = root->right; while (!stk.empty()) { if (t1 == nullptr && t2 != nullptr || t1 != nullptr && t2 == nullptr) { //1.在新的一轮开始时要判断 "t1父结点的right,t2父结点的left"作为新的root的两个结点(新一轮初始时判断) return false; } while (t1 != nullptr && t2 != nullptr) { //从当前树,不断向下查找比较left和right结点,无非三种情况:(1)t1,t2都为nullptr,返回flase; (2)t1,t2只有一个为nullptr,返回false; (3)t1,t2都不为nullptr,值相对返回true,否则返回false; if (t1->val != t2->val) { //能正常从循环中出来,说明t1==nullptr,t2==nullptr,否则都会返回false。 return false; } stk.push(t1); stk.push(t2); t1 = t1->left; //不断查找判断 左和右 结点 t2 = t2->right; if (t1 == nullptr && t2 != nullptr || t1 != nullptr && t2 == nullptr) { //2.在向下查找比较 左和右结点 时要判断(过程中判断) return false; } } t2 = stk.top()->left; //当t1==nullptr,t2==nullptr,此应该继续查找 "t1父结点的right,t2父结点的left ",并且t1,t2各自的父结点作为root的俩颗子树,继续查找比较 左和右 结点 stk.pop(); //注:由于是栈的缘故,开始是t1、t2入栈,此时应该以t2、t1顺序接收出栈值(stk.top()即他们的父结点) t1 = stk.top()->right; stk.pop(); } return true; */ //(迭代,队列实现) std::queue<TreeNode*>Q; Q.push(root->left); Q.push(root->right); while (!Q.empty()) { //结合广度优先遍历理解,只不过这里是同时处理俩组(左右,右左),但也是一层一层处理 TreeNode* t1 = Q.front(); //和广度优先遍历类似,当t1,t2作为父结点的时候应移除队列,并且在此时进行比较 Q.pop(); TreeNode* t2 = Q.front(); Q.pop(); if (t1 == nullptr && t2 == nullptr) { continue; } if (t1 == nullptr || t2 == nullptr || t1->val != t2->val) { return false; } Q.push(t1->left); //对于两个位置对称的父结点t1,t2来说:t1->left和t2->right;t1->right和t2->left是两组位置对称的子结点,将他们加入队列 Q.push(t2->right); Q.push(t1->right); Q.push(t2->left); } return true; } }; bool Solution::is_hui_wen(std::deque<TreeNode*> DQ_t) { //判断是否为构成回文 while (!DQ_t.empty()) { TreeNode* first = DQ_t.front(); TreeNode* last = DQ_t.back(); if (first->val != last->val) { return false; } if (DQ_t.size()>1) { DQ_t.pop_front(); DQ_t.pop_back(); } else { DQ_t.pop_front(); } } return true; } bool Solution::is_hui_wen(std::vector<TreeNode*>vec) { //判断是否为构成回文 auto first = vec.begin(); auto last = vec.end()-1; while (first < last) { if ((*first != nullptr && *last != nullptr) && (*first)->val != (*last)->val) { //first、last是有效的结点则比较值,不等则不构成回文,返回false return false; } if ((*first == nullptr && *last != nullptr) || (*first != nullptr && *last == nullptr)) { //first、last其中一个为有效结点,一个为无效结点,则肯定不构成回文,返回false return false; //满足条件情况:first、last都为无效结点 或者 都为有效结点且值相等 } first++; last--; } return true; } bool Solution::compare(TreeNode* t1, TreeNode* t2) { //t1,t2两个结点是对称结点(位置对称) if (t1 != nullptr && t2 != nullptr) { //如果是对称二叉树,最起码每个结点都有一个与之位置对称的结点 if (t1->val == t2->val) { //每个结点不仅要有位置上对称的结点,而且结点值要相等。在满足这样的条件下才有必要继续递归比较下去 if (!compare(t1->left, t2->right)) { //可知t1和t2是两个位置对称结点(从root开始推就可知,t1处于root的左,t2处于root的右),所以接下来要比较t1,t2这俩个结点的子结点也必须对称,所以t1->left对应的是t2->right,t1->right对应的是t2->left。(一个左一个右才呈对称位置) return false; //如果compare函数返回false则直接向上一层返回false } if (!compare(t1->right, t2->left)) { return false; } } else { //如果对称位置上两个结点值不等,返回false return false; } } if ((t1 == nullptr && t2 != nullptr) || (t1 != nullptr && t2 == nullptr)) { //如果一个结点连对称位置上都不存在相应对称结点,肯定不满足对称二叉树,直接返回false return false; } return true; //上面俩个if如果都不满足,说明t1,t2都为nullptr,说明这一递归层的对应位置都是空结点,直接返回上一层,所以返回true } bool Solution::check(TreeNode* t1, TreeNode* t2) { //官方代码简化版,无非三种情况: (1)t1,t2都为nullptr,返回flase; (2)t1,t2只有一个为nullptr,返回false; (3)t1,t2都不为nullptr,值相对返回true,否则返回false; //我是按(3)(2)(1)顺序写,(3)(2)为俩个if,剩下自然是(1),放在return。官方an(1)(2)(3),先写明(1)(2)俩代码量最少的,剩下情况自然就是(3),(3)代码量最多,放在return,省去了冗余的判断,因为(1)(2)判断完,剩下肯定是(3)这种情况不用再次判断 if (t1 == nullptr && t2 == nullptr) { return true; } if (t1 == nullptr || t2 == nullptr) { //根据上面判断知道,到这里说明t1,t2不可能都为nullptr,所以判断t1,t2是否只有一个为nullptr时,只需要判断t1==nullptr或t2==nullptr就行,满足就返回flase return false; } return (t1->val == t2->val) && check(t1->left, t2->right) && check(t1->right, t2->left); //因为经过了(1)(2)判断这里默认就是(3)成立,即t1,t2都不为空。利用了&&的特点,先把(t1->val == t2->val)放前面,满足才会判断后来俩个函数,即继续递归,否则直接跳过后面俩个函数返回flase,正好和(3)对上。 }
二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。示例:
二叉树:[3,9,20,null,null,15,7],3/ \9 20/ \15 7返回其层序遍历结果:[[3],[9,20],[15,7]]
相关标签:树 广度优先搜索 二叉树
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
#pragma once #include<vector> #include<queue> using std::vector; //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>>& helper(TreeNode* root, int level, vector<vector<int>>& vec); vector<vector<int>> levelOrder(TreeNode* root) { /*by myself(队列实现) std::queue<TreeNode*>Q; vector<vector<int>>vec; int level = 0; if (root == nullptr) { //root为空要返回,否则下面语句 if(t->left != nullptr) 非法引用 return vec; } Q.push(root); while (!Q.empty()) { int nodes_level = Q.size(); vec.resize(level + 1); //vector<vector<int>>vec;这样定义的vec没有分配空间,不能够直接vec[1]!!!。两种方法:(1)vec.resize(level + 1) (2)vec.push_back(vector <int> ())。emmm,后面这种更加C++ while (nodes_level) { TreeNode* t = Q.front(); vec[level].push_back(t->val); //每一层对应二维数组vec的每一个元素(即一维数组),这里遍历每一层结点时,只需要将其插入其所在层对应的一维数组中 Q.pop(); if (t->left != nullptr) { Q.push(t->left); } if (t->right != nullptr) { Q.push(t->right); } nodes_level--; } level++; } return vec; */ //by myself(递归) vector<vector<int>>vec; return helper(root, 0, vec); } }; vector<vector<int>>& Solution::helper(TreeNode* root, int level, vector<vector<int>>& vec) { //因为要层序遍历,所有这把结点所在层数level作为参数传递,传递vec参数目的为了传引用,以免因递归调用而导致多次构造数组带来的浪费 if (root == nullptr) { return vec; } if (level + 1 > vec.size()) { //要判断在该递归层level时,vec.size()如果小于level+1,则说明该层对应的一维数组空间还不存在,所以要对vec进行扩容 vec.resize(level + 1); } vec[level].push_back(root->val); //采用先序遍历,将结点插入其所在层对应的一维数组中(直接插入后面就行,不管是先序、中序、后续遍历,其本质都是从左往右遍历) helper(root->left, level + 1, vec); helper(root->right, level + 1, vec); return vec; }
将有序数组转换为二叉搜索树
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
输入:nums = [-10,-3,0,5,9]输出:[0,-3,9,-10,null,5]解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
示例 2:
输入:nums = [1,3]输出:[3,1]解释:[1,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
1 <= nums.length <= 104-104 <= nums[i] <= 104nums 按 严格递增 顺序排列
相关标签:树 二叉搜索树 数组 分治 二叉树
#pragma once #include<vector> using std::vector; //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* helper(const std::vector<int>& vec, int left, int right); TreeNode* sortedArrayToBST(vector<int>& nums) { /*(错误实例) //泪目,想用贪心,贪错了。。 (原本思路:首先确顶root(在这里是3,即数组中间元素),如果vec.size()>3,那么再确定root->left,root->right(在这里分别为2、5,left即数组中间元素的前一个,right为数组最后一个),root->left之前的全部接在root->left结点左边,root到root->right之前的结点,全部接在root->right左边。 //这样构造出的树虽然满足搜索树,但是"不是完全的平衡二叉树(AVL)",AVL对所有节点作为root的子树时也都必须满足AVL!!!(即每个节点的左右两个子树的高度差的绝对值不超过 1) // 输出结果 预期结果 数组vec={0,1,2,3,4,5} int root_index = nums.size() / 2; // 3 3 TreeNode* root = new TreeNode(nums[root_index]); // / \ / \ // 2 5 1 5 TreeNode* t = root; // / / / \ / for (int i = root_index - 1; i >= 0; i--) { // 1 4 0 2 4 TreeNode* p = new TreeNode(nums[i]); // / t->left = p; // 0 t = p; } if (root_index != nums.size() - 1) { TreeNode* p = new TreeNode(nums[nums.size() - 1]); root->right = p; t = p; for (int i = nums.size() - 2; i > root_index; i--) { TreeNode* p = new TreeNode(nums[i]); t->left = p; t = p; } } return root; */ //(中序遍历,递归) return helper(nums, 0, nums.size() - 1); //二叉搜索树的中序遍历是升序序列,题目给定的数组是按照升序排序的有序数组,因此可以确保数组是二叉搜索树的中序遍历序列。 //注:就算给定二叉搜索树的中序遍历,也不可以唯一地确定二叉搜索树,这里再子树结点为偶数数时,总是选择中间位置左边作为root,中间位置右边也可以,如果这两种可能再在每一个递归层(也就是当前层子树)交错选择,会导致多种不同结果。 } }; TreeNode* Solution::helper(const std::vector<int>& vec, int left, int right) { if (left > right) { return nullptr; } int mid = (right + left) / 2; TreeNode* root = new TreeNode(vec[mid]); //总选择中间位置(奇个)或者左边(偶个)作为该所在层子树的root。确定平衡二叉搜索树的根节点之后,其余的数字分别位于平衡二叉搜索树的左子树和右子树中,左子树和右子树分别也是平衡二叉搜索树,因此可以通过递归的方式创建平衡二叉搜索树。 //为什么这么建树一定能保证是「平衡」的呢? 简单脑部理解就是:这样递归也是一直二分下去,分一半结点给左边以left为根结点建立左子树,分一半给右边以right为根结点建立右子树。按递归走只是先建立左再建立右,但是每一层的左右子树在每一层都早已经二分好了,最后得到的树是平衡二叉树 root->left = helper(vec, left, mid - 1); root->right = helper(vec, mid + 1, right); return root; }