【数据结构与算法】二叉树整合[leetcode]
递归
平衡二叉树
LeetCode:平衡二叉
题目描述:
给定一个二叉树,判断它是否是高度平衡的二叉树。
示例:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回true
思想:
使用实例域变量记录是否与有不满足平衡的节点出现,使用一次递归即可。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private boolean flag = true;
//返回高度,并且在递归过程中记录flag
private int treeDepth(TreeNode root){
if(root ==null)
return 0;
int leftLen = treeDepth(root.left);
int rightLen = treeDepth(root.right);
if(Math.abs(leftLen-rightLen)>1)
flag = false;
return Math.max(leftLen,rightLen)+1;
}
public boolean isBalanced(TreeNode root) {
int i = treeDepth(root);
return flag;
}
}
二叉树的直径
LeetCode:二叉树的直径
题目描述:
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例:
给定二叉树
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
思想:
每一个结点的左子树深度+右子树深度之和,最大的一个,就是结果。完全可以在递归中做到。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private int maxPath=0;
public int diameterOfBinaryTree(TreeNode root) {
treeDepth(root);
return maxPath;
}
private int treeDepth(TreeNode root){
if(root==null)
return 0;
int x=treeDepth(root.left);
int y=treeDepth(root.right);
maxPath=Math.max(x+y,maxPath);
return Math.max(x,y)+1;
}
}
路径总和
LeetCode:路径总和 III
题目描述:
给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过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
思想:
使用双层递归,第一层递归遍历每个结点,从每个结点开始递归寻找以该结点为起点的路径。
注意:找到合适路径末端时,不要着急return,继续向下,因为有些路径是重叠的(起点一致,终点不一样)
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int pathSum(TreeNode root, int sum) {
if(root == null) return 0;
return pathSumStartWithRoot(root,sum)+pathSum(root.left,sum)+pathSum(root.right,sum);
}
private int pathSumStartWithRoot(TreeNode root, int sum){
if(root == null) return 0;
int ret = (sum == root.val)?1:0;
return ret+pathSumStartWithRoot(root.left,sum-root.val)+pathSumStartWithRoot(root.right,sum-root.val);
}
}
另一棵树的子树
LeetCode:另一棵树的子树
题目描述:
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
示例:
给定的树 s:
3
/ \
4 5
/ \
1 2
给定的树 t:
4
/ \
1 2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。
思想:
双递归,注意下面的代码
if(!isEqual(s,t)){
return isSubtree(s.left,t)||isSubtree(s.right,t);
}
return true;
可以简化为:
return isEqual(s,t)||isSubtree(s.left,t)||isSubtree(s.right,t);
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private boolean isEqual(TreeNode s, TreeNode t){
if(s==null&&t==null)
return true;
if(s==null||t==null||s.val!=t.val)
return false;
return isEqual(s.left,t.left)&&isEqual(s.right,t.right);
}
public boolean isSubtree(TreeNode s, TreeNode t) {
if(s==null&&t!=null)
return false;
return isEqual(s,t)||isSubtree(s.left,t)||isSubtree(s.right,t);
}
}
对称二叉树
LeetCode:对称二叉树
题目描述:
给定一个二叉树,检查它是否是镜像对称的。
示例:
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
返回true
思想:
1.怎么判断一棵树是不是对称二叉树? 答案:如果所给根节点,为空,那么是对称。如果不为空的话,当他的左子树与右子树对称时,他对称
2.那么怎么知道左子树与右子树对不对称呢?在这我直接叫为左树和右树 答案:如果左树的左孩子与右树的右孩子对称,左树的右孩子与右树的左孩子对称,那么这个左树和右树就对称。
仔细一想就明白了,递归写起来很简单。
代码
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
return isSonSymmetric(root.left, root.right);
}
private boolean isSonSymmetric(TreeNode m, TreeNode n){
if(m==null&&n==null)
return true;
if(m==null||n==null||m.val!=n.val)
return false;
return isSonSymmetric(m.left,n.right)&&isSonSymmetric(m.right,n.left);
}
}
二叉树的最小深度
LeetCode:二叉树的最小深度
题目描述:
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最小深度 2.
思想:
还是递归,没啥特别的
代码
我的第一遍代码:
public int minDepth(TreeNode root) {
if(root==null)
return 0;
if(root.left==null&&root.right==null)
return 1;
if(root.left==null)
return 1+minDepth(root.right);
if(root.right==null)
return 1+minDepth(root.left);
return 1+Math.min(minDepth(root.left),minDepth(root.right));
}
优化之后:
class Solution {
public int minDepth(TreeNode root) {
if(root==null) return 0;
int x=minDepth(root.right);
int y=minDepth(root.left);
if(x==0||y==0)
return x+y+1;
return Math.min(x,y)+1;
}
}
if(x == 0||y==0) return x+y+1; 非常妙
最长同值路径
LeetCode:最长同值路径
题目描述:
给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
注意:两个节点之间的路径长度由它们之间的边数表示。
示例:
输入:
1
/ \
4 5
/ \ \
4 4 5
输出:
2
思想:
可以设置一个全局变量res,只需要一次递归完成。递归过程中更新这个全局变量。
每递归遍历一个结点,可以得到从左开始的最大长度leftLen,和从右开始的最大长度rightLen,两者相加用于更新res,取max用于返回到上一层递归。
代码
- 笨办法
class Solution {
private int maxLen(TreeNode root,int val){
if(root==null||root.val != val)
return 0;
return 1+Math.max(maxLen(root.left,val),maxLen(root.right,val));
}
public int longestUnivaluePath(TreeNode root) {
if(root == null)
return 0;
int L = maxLen(root.left,root.val)+maxLen(root.right,root.val);
return Math.max(L,Math.max(longestUnivaluePath(root.left),longestUnivaluePath(root.right)));
}
}
- 巧妙方法
class Solution {
private int res = 0;
private int dfs(TreeNode root){
if(root==null)
return 0;
//以下一个结点为起点的最长路径值
int left = dfs(root.left);
int right = dfs(root.right);
//若当前结点root值与下一个结点相等,则加1
int leftLen = root.left!=null&&root.val==root.left.val?(left+1):0;
int rightLen = root.right!=null&&root.val==root.right.val?(right+1):0;
//leftLen + rightLen两个加起来,就是经过root的最长路径值
res = (leftLen + rightLen)>res?(leftLen + rightLen):res;
return Math.max(leftLen,rightLen);//以root为起点的最长路径值
}
public int longestUnivaluePath(TreeNode root) {
dfs(root);
return res;
}
}
间隔遍历-打家劫舍
LeetCode:打家劫舍 III
题目描述:
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
思想:
A
/ \
B C
/ \ \
D E F
间隔遍历,每次递归时,有两种情况,A加上DEF开始的结果,或者B和C为根的结果之和。第二种情况可能B和C都不去偷,但这个结果一定被第一种情况囊括了,换句话说,一定是小于第一种情况的值。(貌似说得有点含糊)
代码
笨方法:
class Solution {
private int max(TreeNode root , boolean steal){
if(root==null) return 0;
if(steal){
return root.val + max(root.left,false)+max(root.right,false);
}else{
return Math.max(max(root.left,false),max(root.left,true))+Math.max(max(root.right,false),max(root.right,true));
}
}
public int rob(TreeNode root) {
return Math.max(max(root,false),max(root,true));
}
}
正确方法:
class Solution {
public int rob(TreeNode root) {
if(root==null) return 0;
int val1 = root.val;
if(root.left!=null) val1+=rob(root.left.right)+rob(root.left.left);
if(root.right!=null) val1+=rob(root.right.left)+rob(root.right.right);
int val2 = rob(root.left)+rob(root.right);
return Math.max(val1,val2);
}
}
方法二比方法一好的地方在于:方法二每个结点最多只遍历了两遍,而方法一中,每次遍历都要以两种状态考虑一个结点,偷或者不偷,所以第一个结点访问2次,第二层每个结点访问1+2次(上一个结点选择偷,分裂出1次,选择不偷,分裂出2次),第3层的结点要访问3+2呢,第n层访问多少次呢,不算了,头要裂开,反正一定比方法一多了去。
二叉树中第二小的结点
LeetCode:二叉树中第二小的结点
题目描述:
给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么这个节点的值不大于它的子节点的值。
给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。
示例:
输入:
2
/ \
2 5
/ \
5 7
输出: 5
说明: 最小的值是 2 ,第二小的值是 5 。
思想:
注意审题,子节点数量只可能是0或者2
递归找不到比根更大的值时,就继续递归,一直找不到就返回-1;若找到比根更大的数就记录下来,停止递归,对左右两个结果做个比较。
代码
class Solution {
public int findSecondMinimumValue(TreeNode root) {
if(root==null) return -1;
int leftMin,rightMin;
if(root.left==null&&root.right==null)
return -1;
leftMin = root.left.val == root.val?findSecondMinimumValue(root.left):root.left.val;
rightMin = root.right.val == root.val?findSecondMinimumValue(root.right):root.right.val;
return leftMin>0&&rightMin>0?Math.min(leftMin,rightMin):Math.max(leftMin,rightMin);
}
}
队列与栈的辅助
二叉树的层平均值
LeetCode:二叉树的层平均值
题目描述:
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组.
示例:
输入:
3
/ \
9 20
/ \
15 7
输出: [3, 14.5, 11]
解释:
第0层的平均值是 3, 第1层是 14.5, 第2层是 11. 因此返回 [3, 14.5, 11].
思想:
使用一个队列进行层次遍历,BFS。具体思路如下:
- 首先外层一定有一个循环,循环开始时,队列存储这一层所有节点,循环体末尾,队列存储好下一层所有节点;
- 循环内部需要做的:再用一层循环,执行出队,累加求和取平均值,同时将出队节点的左右孩子节点再次加入队列;
- 如果内层循环条件是isEmpty(),循环无法在上一层节点遍历完毕时结束。所以应该用size()记录队列长度,作为循环结束的条件。
代码
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<>();
if(root==null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
double n = queue.size(),m=0;
for(int i=0;i<n;++i){
TreeNode temp = queue.poll();
m+=temp.val;
if(temp.left!=null) queue.add(temp.left);
if(temp.right!=null) queue.add(temp.right);
}
res.add(m/n);
}
return res;
}
}
找树左下角的值
LeetCode:找树左下角的值
题目描述:
给定一个二叉树,在树的最后一行找到最左边的值。
示例:
输入:
2
/ \
1 3
输出:
1
思想:
依然使用一个队列进行BFS,注意是从右往左。
代码
class Solution {
public int findBottomLeftValue(TreeNode root) {
TreeNode temp=null;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
temp = queue.poll();
if(temp.right!=null) queue.add(temp.right);
if(temp.left!=null) queue.add(temp.left);
}
return temp.val;
}
}
二叉树的前中后序遍历
LeetCode:二叉树的前序遍历
LeetCode:二叉树的中序遍历
LeetCode:二叉树的后序遍历
题目描述:
前中后序遍历
思想:
迭代思想,都需要使用栈来辅助。
- 前序遍历:根左右;循环体的思想很直白,先pop出一个结点,计入val,再push进右孩子结点和左孩子结点,进入下一次循环。
- 中序遍历:左根右;首先想到,每一次循环中,有一个TreeNode指针可以指代一个结点,和一个stack能pop出一个结点,这两个结点可以不一样的,都利用起来;让指针指向刚入栈的左孩子,或者刚出栈的右孩子;可以想到,判断条件设置为指针指代的结点是否为空,如果不为空就将该结点入栈,再指向左孩子,如果为空,则出栈一个结点,计入出栈结点的val,再让指针指向出栈结点的右孩子;注意while条件中要加一个root!=null。
- 后序遍历:左右根;这个也很巧妙,按照前序遍历的方法,修改为根右左,每次计val的时候逆序插入(前插),最后就能得到后序遍历结果。
代码
- 前序遍历
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root==null) return res;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
root = stack.pop();
if(root==null) continue;
res.add(root.val);
stack.push(root.right);
stack.push(root.left);
}
return res;
}
}
- 中序遍历
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
List<Integer> res = new ArrayList<>();
while(!stack.isEmpty()||root!=null){
if(root==null){
root = stack.pop();
res.add(root.val);
root = root.right;
}else{
stack.push(root);
root=root.left;
}
}
return res;
}
}
- 后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root==null) return res;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
root = stack.pop();
if(root.left!=null) stack.push(root.left);
if(root.right!=null) stack.push(root.right);
res.add(0,root.val);//逆序添加结点值
}
return res;
}
}