二叉树1
leetcode 二叉树
树的遍历
前序遍历
前序遍历首先访问根节点,然后遍历左子树,最后遍历右子树。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> nodeList = new ArrayList<>();
traversal(root, nodeList);
return nodeList;
}
public void traversal(TreeNode root, List<Integer> nodeList){
if(root == null){return;}
//前序遍历
nodeList.add(root.val);
traversal(root.left, nodeList);
traversal(root.right, nodeList);
}
}
中序遍历
中序遍历是先遍历左子树,然后访问根节点,然后遍历右子树。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> nodeList = new ArrayList<>();
traversal(root, nodeList);
return nodeList;
}
public void traversal(TreeNode root, List<Integer> nodeList){
if(root == null){return;}
//中序遍历
traversal(root.left, nodeList);
nodeList.add(root.val);
traversal(root.right, nodeList);
}
}
后序遍历
后序遍历是先遍历左子树,然后遍历右子树,最后访问树的根节点。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> nodeList = new ArrayList<>();
traversal(root, nodeList);
return nodeList;
}
public void traversal(TreeNode root, List<Integer> nodeList){
if(root == null){return;}
//后序遍历
traversal(root.left, nodeList);
traversal(root.right, nodeList);
nodeList.add(root.val);
}
}
层序遍历
层序遍历就是逐层遍历树结构。
广度优先搜索是一种广泛运用在树或图这类数据结构中,遍历或搜索的算法。 该算法从一个根节点开始,首先访问节点本身。 然后遍历它的相邻节点,其次遍历它的二级邻节点、三级邻节点,以此类推。
当我们在树中进行广度优先搜索时,我们访问的节点的顺序是按照层序遍历顺序的。
通常,我们使用一个叫做队列的数据结构来帮助我们做广度优先搜索。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if(root != null){
queue.add(root);
}
while(!queue.isEmpty()){
int n = queue.size();
List<Integer> level = new ArrayList<>();
for(int i = 0; i < n; i++){
TreeNode node = queue.poll();
level.add(node.val);
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
}
res.add(level);
}
return res;
}
}
递归来解决树问题
通常,我们可以通过 “自顶向下” 或 “自底向上” 的递归来解决树问题
当遇到树问题时,请先思考一下两个问题:
你能确定一些参数,从该节点自身解决出发寻找答案吗?
你可以使用这些参数和节点本身的值来决定什么应该是传递给它子节点的参数吗?
如果答案都是肯定的,那么请尝试使用 “自顶向下” 的递归来解决此问题。
或者你可以这样思考:对于树中的任意一个节点,如果你知道它子节点的答案,你能计算出该节点的答案吗? 如果答案是肯定的,那么 “自底向上” 的递归可能是一个不错的解决方法。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/data-structure-binary-tree/xefb4e/
来源:力扣(LeetCode)
“自顶向下” 的解决方案
“自顶向下” 意味着在每个递归层级,我们将首先访问节点来计算一些值,并在递归调用函数时将这些值传递到子节点。 所以 “自顶向下” 的解决方案可以被认为是一种前序遍历。 具体来说,递归函数 top_down(root, params) 的原理是这样的:
1. return specific value for null node
2. update the answer if needed // answer <-- params
3. left_ans = top_down(root.left, left_params) // left_params <-- root.val, params
4. right_ans = top_down(root.right, right_params) // right_params <-- root.val, params
5. return the answer if needed // answer <-- left_ans, right_ans
剑指 Offer 55 - I. 二叉树的深度
https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/
class Solution {
public int maxDepth(TreeNode root) {
if (null == root){
return 0;
}
int leftDeep = maxDepth(root.left );
int rightDeep = maxDepth(root.right);
return Math.max(leftDeep, rightDeep) + 1;
}
}
101. 对称二叉树
https://leetcode-cn.com/problems/symmetric-tree/
解法:
镜像对称也就是左子树和右子树是相同的。
将根节点的左子树记做 left,右子树记做 right。比较 left 是否等于 right,不等的话直接返回就可以了。如果相同,比较 left 的左节点和 right 的右节点,再比较 left 的右节点和 right 的左节点
规律:
左子树的左孩子 == 右子树的右孩子
左子树的右孩子 == 右子树的左孩子
class Solution {
public boolean isSymmetric(TreeNode root) {
if(null == root){
return true;
}
return help(root.left, root.right);
}
public boolean help(TreeNode left, TreeNode right){
if(left == null && right == null){
return true;
}
if(left == null || right == null){
return false;
}
if(left.val == right.val){
return true;
}
return help(left.left, right.right) && help(left.right, right.left);
}
}
复杂度分析
假设树上一共 n 个节点。
时间复杂度:这里遍历了这棵树,渐进时间复杂度为 O(n)。
空间复杂度:这里的空间复杂度和递归使用的栈空间有关,这里递归层数不超过 n,故渐进空间复杂度为 O(n)。
leetcode上关于二叉树路径的解题套路文章:
作者:eh-xing-qing
链接:https://leetcode-cn.com/problems/path-sum/solution/yi-pian-wen-zhang-jie-jue-suo-you-er-cha-pqum/
来源:力扣(LeetCode)
问题分类
二叉树路径的问题大致可以分为两类:
1、自顶向下:
顾名思义,就是从某一个节点(不一定是根节点),从上向下寻找路径,到某一个节点(不一定是叶节点)结束
具体题目如下:
257. 二叉树的所有路径
面试题 04.12. 求和路径
112. 路径总和
113. 路径总和 II
437. 路径总和 III
988. 从叶结点开始的最小字符串
而继续细分的话还可以分成一般路径与给定和的路径
2、非自顶向下:
就是从任意节点到任意节点的路径,不需要自顶向下
124. 二叉树中的最大路径和
687. 最长同值路径
543. 二叉树的直径
解题模板
这类题通常用深度优先搜索(DFS)和广度优先搜索(BFS)解决,BFS较DFS繁琐,这里为了简洁只展现DFS代码
下面是我对两类题目的分析与模板
一、自顶而下:
DFS
//一般路径:
vector<vector<int>>res;
void dfs(TreeNode*root,vector<int>path)
{
if(!root) return; //根节点为空直接返回
path.push_back(root->val); //作出选择
if(!root->left && !root->right) //如果到叶节点
{
res.push_back(path);
return;
}
dfs(root->left,path); //继续递归
dfs(root->right,path);
}
//给定和的路径
void dfs(TreeNode*root, int sum, vector<int> path)
{
if (!root)
return;
sum -= root->val;
path.push_back(root->val);
if (!root->left && !root->right && sum == 0)
{
res.push_back(path);
return;
}
dfs(root->left, sum, path);
dfs(root->right, sum, path);
}
这类题型DFS注意点:
1、如果是找路径和等于给定target的路径的,那么可以不用新增一个临时变量cursum来判断当前路径和,
只需要用给定和target减去节点值,最终结束条件判断target==0即可
2、是否要回溯:二叉树的问题大部分是不需要回溯的,原因如下:
二叉树的递归部分:dfs(root->left),dfs(root->right)已经把可能的路径穷尽了,
因此到任意叶节点的路径只可能有一条,绝对不可能出现另外的路径也到这个满足条件的叶节点的;
而对比二维数组(例如迷宫问题)的DFS,for循环向四个方向查找每次只能朝向一个方向,并没有穷尽路径,
因此某一个满足条件的点可能是有多条路径到该点的
并且visited数组标记已经走过的路径是会受到另外路径是否访问的影响,这时候必须回溯
3、找到路径后是否要return:
取决于题目是否要求找到叶节点满足条件的路径,如果必须到叶节点,那么就要return;
但如果是到任意节点都可以,那么必不能return,因为这条路径下面还可能有更深的路径满足条件,还要在此基础上继续递归
4、是否要双重递归(即调用根节点的dfs函数后,继续调用根左右节点的pathsum函数):看题目要不要求从根节点开始的,还是从任意节点开始
二、非自顶而下:
这类题目一般解题思路如下:
设计一个辅助函数maxpath,调用自身求出以一个节点为根节点的左侧最长路径left和右侧最长路径right,那么经过该节点的最长路径就是left+right
接着只需要从根节点开始dfs,不断比较更新全局变量即可
int res=0;
int maxPath(TreeNode *root) //以root为路径起始点的最长路径
{
if (!root)
return 0;
int left=maxPath(root->left);
int right=maxPath(root->right);
res = max(res, left + right + root->val); //更新全局变量
return max(left, right); //返回左右路径较长者
}
这类题型DFS注意点:
1、left,right代表的含义要根据题目所求设置,比如最长路径、最大路径和等等
2、全局变量res的初值设置是0还是INT_MIN要看题目节点是否存在负值,如果存在就用INT_MIN,否则就是0
3、注意两点之间路径为1,因此一个点是不能构成路径的
112. 路径总和
112. 路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。
解法:
- 叶子节无左右子节点,当题目中提到了叶子节点时,正确的做法一定要同时判断节点的左右子树同时为空才是叶子节点
- 当前节点的值为root.val, 则该节点子树路径和为sum - root.val,大问题转化为小问题,即每一个节点下子树的路径和是否满足条件,若该节点不是叶子节点,则递归该求解该节点下其他子节点是否满足条件,若该节点为叶子节点,则直接判断是否等于目标值
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return sum == root.val;
}
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
257. 二叉树的所有路径
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
if(null ==root) return new ArrayList<>();
List<String> res = new ArrayList<>();
help(root, "", res);
return res;
}
public void help(TreeNode root, String path, List<String>paths){
if(null != root){
StringBuilder sb = new StringBuilder(path);
sb.append(Integer.toString(root.val));
if(root.left == null && root.right == null){
paths.add(sb.toString());
}else{
sb.append("->");
help(root.left, sb.toString(), paths);
help(root.right, sb.toString(), paths);
}
}
}
}
面试题 04.12. 求和路径
https://leetcode-cn.com/problems/paths-with-sum-lcci/
class Solution {
int count = 0;
public int pathSum(TreeNode root, int sum) {
if (null == root){return 0;}
dfs(root, sum);
pathSum(root.left, sum);
pathSum(root.right, sum);
return count;
}
void dfs (TreeNode root, int sum){
if (null == root){return;}
sum -= root.val;
if(sum == 0){
count++;
}
dfs(root.left, sum);
dfs(root.right, sum);
}
}
988. 从叶结点开始的最小字符串
https://leetcode-cn.com/problems/smallest-string-starting-from-leaf/
class Solution {
String ans = "~";
public String smallestFromLeaf(TreeNode root) {
dfs(root, new StringBuilder());
return ans;
}
public void dfs(TreeNode root, StringBuilder sb){
if (null == root) return;
sb.append((char)('a'+ root.val));
if (root.left == null && root.right == null){
sb.reverse();
String S = sb.toString();
sb.reverse();
if(S.compareTo(ans) < 0){
ans = S;
}
}
dfs(root.left, sb);
dfs(root.right, sb);
sb.deleteCharAt(sb.length() - 1);
}
}
124. 二叉树中的最大路径和 hard
class Solution {
int ans =Integer.MIN_VALUE;;
public int maxPathSum(TreeNode root) {
maxPath(root);
return ans;
}
public int maxPath(TreeNode root){
if(null == root){
return 0;
}
int left = Math.max(maxPath(root.left), 0);
int right = Math.max(maxPath(root.right), 0);
ans = Math.max(ans, left + right + root.val);
return Math.max(left + root.val, right + root.val);
}
}
687. 最长同值路径
https://leetcode-cn.com/problems/longest-univalue-path/
*/
class Solution {
int ans = 0;
public int longestUnivaluePath(TreeNode root) {
if (null == root){return 0;}
path(root);
return ans;
}
public int path(TreeNode root){
if (null == root) {return 0;}
int left = path(root.left);
int right = path(root.right);
if (root.left != null && root.left.val == root.val){
left += 1;
}else{
left = 0;
}
if (root.right !=null && root.right.val == root.val){
right += 1;
}else{
right = 0;
}
ans = Math.max(left + right, ans);
return Math.max(left, right);
}
}
543. 二叉树的直径
class Solution {
int ans = 0;
public int diameterOfBinaryTree(TreeNode root) {
max(root);
return ans;
}
public int max (TreeNode root){
if(null == root) {return 0;}
int left = max(root.left);
int right = max(root.right);
if(null !=root.left){
left += 1;
}
if (null != root.right){
right += 1;
}
ans = Math.max(left + right, ans);
return Math.max(left, right) ;
}
}