递归
1、树的高度
104.maximum-depth-of-binary-tree
LeetCode
//2020.6.1
/**
*解法1:
*每个结点只访问一次
*因此时间复杂度为 O(N)
*具体思路就是深度优先遍历左边的树枝,最底层为空时的高度为0,依次+1
*/
class Solution {
public int maxDepth(TreeNode root) {
return getDepth(root);
}
public int getDepth(TreeNode root){
if(root == null) return 0;
int max = 0;
max = Math.max(max,getDepth(root.left)+1);
max = Math.max(max,getDepth(root.right)+1);
return max;
}
}
//简化版写法
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}2、平衡二叉树(BT)
110.balanced-binary-tree
Leetcode
算法流程:
isBalanced(root) :判断树 root 是否平衡
特例处理: 若树根节点 root 为空,则直接返回 true;
返回值: 所有子树都需要满足平衡树性质,因此以下三者使用与逻辑 & 连接;
abs(self.depth(root.left) - self.depth(root.right)) <= 1 :判断当前子树是否是平衡树;
self.isBalanced(root.left) : 先序遍历递归,判断当前子树的左子树 是否是平衡树;
self.isBalanced(root.right) : 先序遍历递归,判断当前子树的右子树 是否是平衡树;
depth(root) : 计算树 root 的最大高度
终止条件: 当 root 为空,即越过叶子节点,则返回高度 0 ;
返回值: 返回左 / 右子树的最大高度加 1 。
复杂度分析:
时间复杂度 O(NlogN): 最差情况下, isBalanced(root) 遍历树所有节点,占用 O(N);判断每个节点的最大高度 depth(root) 需要遍历各子树的所有节点 ,子树的节点数的复杂度为 O(NlogN)
空间复杂度 O(N): 最差情况下(树退化为链表时),系统递归需要使用O(N)的栈空间。
//2020.6.2
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
return Math.abs(getDepth(root.left) - getDepth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
public int getDepth(TreeNode root){
if(root == null) return 0;
return Math.max(getDepth(root.left),getDepth(root.right)) + 1;
}
}3、二叉树的直径
543.diameter-of-binary-tree
LeetCode
算法流程:
depth(root):返回节点的直径
终止条件: 当 root 为空,即越过叶子节点,则返回深度 0 ;
返回值: 返回左 / 右子树的最大高度加 1 。
max = Math.max(left+right,max) : 将每个节点的直径与最大值做比较替换
diameterOfBinaryTree(root):返回节点的最大直径
复杂度分析:
时间复杂度:O(N),N为二叉树节点数,每个节点只访问一遍
空间复杂度:O(Height),Height为树的深度
//2020.6.3
class Solution {
int max;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return max;
}
public int depth(TreeNode root){
if(root == null) return 0;
int left = depth(root.left);
int right = depth(root.right);
max = Math.max(left+right,max);
return Math.max(left,right)+1;
}
}4、翻转二叉树
226.invert-binary-tree
LeetCode
算法流程:
invert(TreeNode root):返回翻转过的二叉树
终止条件: 当 root 为空,即越过叶子节点,则返回 null ;
返回值: 已经翻转过的当前节点 。
TreeNode left = root.left : 将原左儿子节点先保存起来,进入下一步翻转如不保存节点会丢失
root.left = invert(root.right) : 新的左儿子节点赋值为翻转过的原右儿子节点
root.right = invert(left) : 新的右儿子节点赋值为翻转过的原左儿子节点
复杂度分析:
时间复杂度:O(n),树中的每个节点都只被访问一次,n为树中节点个数
空间复杂度:O(n),Height为树的深度
//2020.6.3
class Solution {
public TreeNode invertTree(TreeNode root) {
return invert(root);
}
public TreeNode invert(TreeNode root){
if(root == null) return null;
TreeNode left = root.left;
root.left = invert(root.right);
root.right = invert(left);
return root;
}
}这里贴一个迭代解法。
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root==null) {
return null;
}
//将二叉树中的节点逐层放入队列中,再迭代处理队列中的元素
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(root);
while(!queue.isEmpty()) {
//每次都从队列中拿一个节点,并交换这个节点的左右子树
TreeNode tmp = queue.poll();
TreeNode left = tmp.left;
tmp.left = tmp.right;
tmp.right = left;
//如果当前节点的左子树不为空,则放入队列等待后续处理
if(tmp.left!=null) {
queue.add(tmp.left);
}
//如果当前节点的右子树不为空,则放入队列等待后续处理
if(tmp.right!=null) {
queue.add(tmp.right);
}
}
//返回处理完的根节点
return root;
}
}
作者:wang_ni_ma
链接:https://leetcode-cn.com/problems/invert-binary-tree/solution/dong-hua-yan-shi-liang-chong-shi-xian-226-fan-zhua/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。两种解法的时间复杂度和空间复杂度是一样的
5、合并二叉树
617.merge-two-binary-trees
LeetCode
算法流程:
mergeTrees(TreeNode t1, TreeNode t2):返回合并过的二叉树
终止条件:
有三种情况
- 当 t1,t2 都为空,即两个树上都没有这个节点,那么就不用合并直接返回 null
- 当只有 t1 为空,就要把 t2 接到新建的树上面,返回 t2
- 当只有 t2 为空,就要把 t1 接到新建的树上面,返回 t1
返回值: 已经合并过的二叉树 。
TreeNode root = new TreeNode(t1.val + t2.val) : 当t1,t2都有值的时候,新建一个节点,值为两者之和
root.left= mergeTrees(t1.left,t2.left) : 递归处理左子树
root.right = mergeTrees(t1.right,t2.right) : 递归处理左子树
复杂度分析:
时间复杂度:O(n),n为两个树中节点少的那个个数
空间复杂度:O(n),在最坏情况下,会递归 n 层,需要 O(n) 的栈空间。
//2020.6.4
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if(t1 == null && t2 == null) return null;
if(t1 == null) return t2;
if(t2 == null) return t1;
TreeNode root = new TreeNode(t1.val + t2.val);
root.left= mergeTrees(t1.left,t2.left);
root.right = mergeTrees(t1.right,t2.right);
return root;
}
}6、路径总和
112.path-sum
LeetCode
算法流程:
PathSum(TreeNode root, int sum):返回是否有路径和等于sum
终止条件:
- 当节点为null时,直接返回false
- 当节点为叶子节点,且当前节点的val和sum正好相等时,返回true
- 两者之外的情况就继续递归
复杂度分析:
时间复杂度:O(n),n为树中节点个数
空间复杂度:O(n)
//2020.6.5
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
return PathSum(root,sum);
}
public boolean PathSum(TreeNode root, int sum){
if (root == null) return false;
if (root.left == null &&root.right == null && root.val == sum) return true;
return (PathSum(root.left,sum-root.val) || PathSum(root.right,sum-root.val));
}
}7、统计路径和等于一个数的路径数量
437.path-sum-iii
LeetCode
算法流程:
getSum(TreeNode root, int sum):返回除去root节点,路径和等于sum的情况之和
终止条件:
- 当节点为null时,直接返回0
- 当当前节点的val和sum正好相等时,res的值+1
- 两者之外的情况就继续递归其左右子树
int res = getSum(root,sum) + pathSum(root.left,sum) + pathSum(root.right,sum);
递归遍历root的左右子树,并获取当前节点的除去root节点,路径和等于sum的情况之和
复杂度分析:
时间复杂度:O(nlogn)
空间复杂度:O(n)
//2020.6.8
class Solution {
public int pathSum(TreeNode root, int sum) {
if(root == null) return 0;
int res = getSum(root,sum) + pathSum(root.left,sum) + pathSum(root.right,sum);
return res;
}
public int getSum(TreeNode root, int sum){
if(root == null) return 0;
int res = 0;
if(sum == root.val) res++;
res += getSum(root.left,sum-root.val) + getSum(root.right,sum-root.val);
return res;
}
}8、子树
572.subtree-of-another-tree
LeetCode
算法流程:
isSubtreeWithoutRoot(s,t):返回除去root节点,路径和等于sum的情况之和
终止条件:
- 当节点为null时,直接返回0
- 当当前节点的val和sum正好相等时,res的值+1
- 两者之外的情况就继续递归其左右子树
int res = getSum(root,sum) + pathSum(root.left,sum) + pathSum(root.right,sum);
递归遍历root的左右子树,并获取当前节点的除去root节点,路径和等于sum的情况之和
复杂度分析:
时间复杂度:O(nlogn)
空间复杂度:O(n)
//2020.6.9
class Solution {
public boolean isSubtree(TreeNode s, TreeNode t) {
if(s == null) return false;
return isSubtreeWithoutRoot(s,t) || isSubtree(s.left,t) || isSubtree(s.right,t);
}
public boolean isSubtreeWithoutRoot(TreeNode s, TreeNode t){
if(s == null && t == null) return true;
if(s == null || t == null) return false;
if(s.val != t.val) return false;
return isSubtreeWithoutRoot(s.left,t.left) && isSubtreeWithoutRoot(s.right,t.right);
}
}9、树的对称
101.symmetric-tree
LeetCode
算法流程:
getSum(TreeNode root, int sum):返回除去root节点,路径和等于sum的情况之和
终止条件:
- 当节点为null时,直接返回0
- 当当前节点的val和sum正好相等时,res的值+1
- 两者之外的情况就继续递归其左右子树
int res = getSum(root,sum) + pathSum(root.left,sum) + pathSum(root.right,sum);
递归遍历root的左右子树,并获取当前节点的除去root节点,路径和等于sum的情况之和
复杂度分析:
时间复杂度:O(nlogn)
空间复杂度:O(n)
//2020.6.10
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return dfs(root.left,root.right);
}
public boolean dfs(TreeNode l,TreeNode r){
if(l == null && r == null) return true;
if(l == null || r == null) return false;
if(l.val != r.val) return false;
return dfs(l.left,r.right) && dfs(l.right,r.left);
}
}10.最小路径
111.minimum-depth-of-binary-tree
LeetCode
算法流程:
getSum(TreeNode root, int sum):返回除去root节点,路径和等于sum的情况之和
终止条件:
- 当节点为null时,直接返回0
- 当当前节点的val和sum正好相等时,res的值+1
- 两者之外的情况就继续递归其左右子树
int res = getSum(root,sum) + pathSum(root.left,sum) + pathSum(root.right,sum);
递归遍历root的左右子树,并获取当前节点的除去root节点,路径和等于sum的情况之和
复杂度分析:
时间复杂度:O(nlogn)
空间复杂度:O(n)
//2020.6.10
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
if(root.left == null && root.right == null) return 1;
int m1 = minDepth(root.left);
int m2 = minDepth(root.right);
if(m1 == 0 || m2 == 0) return m1 + m2 + 1;
return Math.min(m1,m2) + 1;
}
}11.左叶子之和
404.sum-of-left-leaves
LeetCode
算法流程:
getSum(TreeNode root, int sum):返回除去root节点,路径和等于sum的情况之和
终止条件:
- 当节点为null时,直接返回0
- 当当前节点的val和sum正好相等时,res的值+1
- 两者之外的情况就继续递归其左右子树
int res = getSum(root,sum) + pathSum(root.left,sum) + pathSum(root.right,sum);
递归遍历root的左右子树,并获取当前节点的除去root节点,路径和等于sum的情况之和
复杂度分析:
时间复杂度:O(nlogn)
空间复杂度:O(n)
//2020.6.11
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if (root == null) return 0;
if(isLeftLeave(root.left)) return root.left.val + sumOfLeftLeaves(root.right);
return sumOfLeftLeaves(root.left) +sumOfLeftLeaves(root.right);
}
public boolean isLeftLeave(TreeNode node){
if(node == null) return false;
return node.left == null && node.right == null;
}
}12.最长同值路径
687.longest-univalue-path
LeetCode
算法流程:
dfs(TreeNode root):返回当前节点下的最长同路径值
终止条件:
- 当节点为null时,直接返回0
- 否则继续进行深度优先遍历
int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0;
深度优先遍历root的左子树到叶子节点的父节点,并判断当前左叶子节点与父节点的值是否相等,相等路径值+1
复杂度分析:
时间复杂度:O(N)
空间复杂度:O(H)
//2020.7.27
class Solution {
private int path = 0;
public int longestUnivaluePath(TreeNode root) {
dfs(root);
return path;
}
public int dfs(TreeNode root){
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0;
int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0;
path = Math.max(path,leftPath+rightPath);
return Math.max(leftPath,rightPath);
}
}13.打家劫舍 III(间隔遍历)
337.house-robber-iii
LeetCode
算法流程:
看到这题我的第一想法就是,要不就是从根开始,要不就是从根的左右节点开始;从根开始的话,此时根的左右节点的值就要跳过,要去算左右节点的儿子节点,这样进行递归就可以解决了,不过递归解法很没面子啊,弟弟解法实锤了,下面贴两个其他解法,比递归提速了不止一点。
终止条件:
- 当节点为null时,直接返回0
- 当左儿子节点不为null时,sum的值加上它的儿子节点
复杂度分析:
时间复杂度:O(N)
空间复杂度:O(H)
//2020.7.28
class Solution {
public int rob(TreeNode root) {
if(root == null) return 0;
int sum = root.val;
if(root.left != null){
sum+=rob(root.left.left) + rob(root.left.right);
}
if(root.right!=null){
sum+=rob(root.right.left) + rob(root.right.right);
}
int sum2 = rob(root.left) + rob(root.right);
return Math.max(sum,sum2);
}
}14.二叉树中第二小的节点
671.second-minimum-node-in-a-binary-tree
LeetCode
//2020年8月2日
class Solution {
public int findSecondMinimumValue(TreeNode root) {
if(root == null) return -1;
return findSMV(root,root.val);
}
public int findSMV(TreeNode x,int rootVal){
if(x.val != rootVal){
return x.val;
}
if(x.left ==null) return -1;
int leftMin = findSMV(x.left,rootVal);
int rightMin = findSMV(x.right,rootVal);
if(leftMin == -1) return rightMin;
if(rightMin == -1) return leftMin;
return Math.min(leftMin,rightMin);
}
}层次遍历
1.二叉树的层平均值
637.average-of-levels-in-binary-tree
LeetCode
就是一个简单的层次遍历,没啥别的
//2020年8月3日
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> ret = new ArrayList<>();
if (root == null) return ret;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int cnt = queue.size();
double sum = 0;
for (int i = 0; i < cnt; i++) {
TreeNode node = queue.poll();
sum += node.val;
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
ret.add(sum / cnt);
}
return ret;
}
}2.得到左下角的节点
513.find-bottom-left-tree-value
LeetCode
对树进行层次遍历,大概就是将节点保存在一个队列中,如果有右儿子,右儿子就先进队,因为队列是先进先出的,我们要保证最后一个是左节点。
//2020年8月4日
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> s = new LinkedList<>();
s.add(root);
while(!s.isEmpty()){
root = s.poll();
if(root.right != null) s.add(root.right);
if(root.left != null) s.add(root.left);
}
return root.val;
}
}前中后序遍历
1 / \ 2 3 / \ \ 4 5 6
- 层次遍历顺序:[1 2 3 4 5 6]
- 前序遍历顺序:[1 2 4 5 3 6]
- 中序遍历顺序:[4 2 5 1 3 6]
- 后序遍历顺序:[4 5 2 6 3 1]
层次遍历使用 BFS 实现,利用的就是 BFS 一层一层遍历的特性;而前序、中序、后序遍历利用了 DFS 实现。
前序、中序、后序遍只是在对节点访问的顺序有一点不同,其它都相同。
1、层次
void bfs(TreeNode root){
Queue<TreeNode> s = new LinkedList<>();
s.add(root);
while(!s.isEmpty()){
root = s.poll();
visit(root);
if(root.left != null) s.add(root.left);
if(root.right != null) s.add(root.right);
}
}2、前序
void dfs(TreeNode root){
visit(root);
dfs(root.left);
dfs(root.right);
}3、中序
void dfs(TreeNode root){
dfs(root.left);
visit(root);
dfs(root.right);
}4、后序
void dfs(TreeNode root){
dfs(root.left);
dfs(root.right);
visit(root);
}1.非递归实现二叉树前序遍历
- 二叉树的前序遍历
LeetCode
//2020年8月6日
class Solution {
List<Integer> ret = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
if(root == null) return ret;
bfs(root);
return ret;
}
public void bfs(TreeNode node){
if(node == null) return;
ret.add(node.val);
if(node.left != null) bfs(node.left);
if(node.right != null) bfs(node.right);
}
}2.非递归实现二叉树后序遍历
- 二叉树的后序遍历
LeetCode
//2020年8月6日
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> s = new Stack<>();
s.push(root);
while(!s.isEmpty()){
TreeNode node = s.pop();
if(node == null) continue;
res.add(node.val);
s.push(node.left);
s.push(node.right);
}
Collections.reverse(res);
return res;
}
}上面的这个算法是有些问题在里面的,LeetCode显示这个算法在时间和空间上都不具备优势,看了一下别人的解,发现递归算法在某些情况下还是具有优势的。
递归算法
3.非递归实现二叉树中序遍历
- 二叉树的中序遍历
LeetCode
这个中序遍历和前两个还不一样,大概的思路是这样的:
“中序遍历是左根右的顺序访问”,所以每访问到一个节点A,要先遍历A节点的左儿子节点,当左儿子全部出栈完毕,A再进行存值和对右儿子的遍历
//2020年8月11日
class Solution {
List<Integer> ret = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root == null) return ret;
Stack<TreeNode> s = new Stack<>();
TreeNode temp = root;
while(!s.isEmpty() || temp != null){
while(temp != null){
s.push(temp);
temp = temp.left;
}
TreeNode node = s.pop();
ret.add(node.val);
temp = node.right;
}
return ret;
}
}二叉查找树(BST)
二叉查找树的定义如下
一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)左、右子树也分别为二叉查找树;
(4)没有键值相等的结点。
1.修剪二叉搜索树
669.修剪二叉搜索树
LeetCode
二叉搜索树也是二叉树,是二叉树的话就可以用递归来解决问题。
先判断出口条件,当当前节点为null,那么直接返回null
剩余的就是判断当前节点的值是否超过了(L,R)这个区间
如果当前节点的val超过了R,说明当前节点的右儿子节点只会比R更大,所以直接返回当前节点的左儿子节点修剪结果
//2020年8月12日
class Solution {
public TreeNode trimBST(TreeNode root, int L, int R) {
if(root == null) return null;
if(root.val > R) return trimBST(root.left,L,R);
if(root.val < L) return trimBST(root.right,L,R);
root.left = trimBST(root.left,L,R);
root.right = trimBST(root.right,L,R);
return root;
}
}2.寻找二叉查找树的第 k 个元素
230.kth-smallest-element-in-a-bst
LeetCode
二叉搜索树也是二叉树,是二叉树的话就可以用递归来解决问题。
先判断出口条件,当当前节点为null,那么直接返回
关键:这是一颗二叉搜索树,二叉搜索树特性决定了它的的中序遍历就是由小到大的顺序,那么我们就可以将其中序遍历存到一个数组里,输出列表的第k-1个元素就好了
//2020年8月14日
class Solution {
List<Integer> list = new ArrayList<>();
public int kthSmallest(TreeNode root, int k) {
inOrder(root,k);
return list.get(k-1);
}
public void inOrder(TreeNode root, int k){
if(root == null) return ;
if(root.left != null) inOrder(root.left,k);
list.add(root.val);
if(root.right != null) inOrder(root.right,k);
}
}
上面的方法易于理解,但是实际会多开一个空间为N的数组,占用的内存就很高,那么有没有不占用内存的方法呢?
当然有了,那就是将遍历到的节点记录下来,当num=k的时候的节点的值,就是我们所需要的值啦!
class Solution {
int num = 0;
int res;
public int kthSmallest(TreeNode root, int k) {
inOrder(root,k);
return res;
}
public void inOrder(TreeNode root, int k){
if(root == null) return ;
if(root.left != null) inOrder(root.left,k);
num++;
if(num == k){res = root.val; return;}
if(root.right != null) inOrder(root.right,k);
}
}
3.把二叉搜索树转换为累加树
538.convert-bst-to-greater-tree
LeetCode
二叉搜索树也是二叉树,是二叉树的话就可以用递归来解决问题。
今天不写解析了,心累了,擦
//2020年8月15日
class Solution {
List<Integer> list = new ArrayList<>();
public TreeNode convertBST(TreeNode root) {
inOrder(root);
Collections.reverse(list);
newInOrder(root);
return root;
}
public void inOrder(TreeNode root){
if(root == null) return;
if(root.left != null) inOrder(root.left);
list.add(root.val);
if(root.right != null) inOrder(root.right);
}
public void newInOrder(TreeNode root){
int i = 0;
if(root == null) return;
if(root.left != null) newInOrder(root.left);
int temp = root.val;
while(temp < list.get(i)){
root.val += list.get(i);
i++;
}
if(root.right != null) newInOrder(root.right);
}
}4.二叉搜索树的最近公共祖先
235.lowest-common-ancestor-of-a-binary-search-tree
LeetCode
分析一下情况可以得知:
共三种情况
- 当p和q在当前根节点的左侧的时候(两节点的值小于root的值),向当前节点左侧深度遍历
- 当p和q在当前根节点的右侧的时候(两节点的值大于root的值),向当前节点右侧深度遍历
- p q节点在当前节点的左右两侧的时候,直接返回当前节点
//2020年8月16日
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);
if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);
return root;
}
}5.二叉树的最近公共祖先
236.lowest-common-ancestor-of-a-binary-tree/submissions
LeetCode
//2020年8月16日
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left == null) return right;
if(right == null) return left;
return root;
}
}
京公网安备 11010502036488号