LeetCode经典算法题目一(字符串、数组、链表、栈、队列、哈希)
六、树
1. 相同的树
树结点类:
public class TreeNode {
int val; //结点值
TreeNode left; //左结点
TreeNode right; //右结点
TreeNode(int x) {
val = x; }
}
算法: 递归。每次比较两个树的当前结点的值,可分四种情况:
- 结点均为空 √
- 一个为空一个不为空 ×
- 均不为空且结点值相同 √
- 均不为空但结点值不同 ×
其中,若为第三种情况就要再比较它们的子树是否相同,即把当前结点的左右子结点再作为参数传递,最后返回左右子树分别比较后相与的结果值。(意思是:必须满足左右子树全部都走通了,没有一个过程返回值为false,最终结果才能返回true)
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null && q==null)
return true;
if(p==null || q==null)
return false;
if(p.val == q.val)
return (isSameTree(p.left,q.left) && isSameTree(p.right,q.right));
return false;
}
}
2. 对称二叉树
算法一: 递归。在同一个类中再写一个比较两树结点的方法,递归调用该方法,每次比较两子树对应结点值。(此题与上一题的区别: 上一题是两棵树进行横向比较;本题是只有一棵树进行内部比较,但使用递归方法我需要把一棵树拆成两棵来比较,方法是:拷贝根结点)这样一来算法思想就跟上题一样,分为四种情况:
- 结点均为空 √
- 一个为空一个不为空 ×
- 均不为空且结点值相同 √
- 均不为空但结点值不同 ×
其中,若为第三种情况就要再比较它们的子树是否对称相同,即把左右结点作为参数传递,最终返回比较后相与的结果值。
注意: 本题跟上题在返回结果处有本质区别,本题由于树是对称的,所以比较的是左结点的左结点与右结点的右结点看是否相同。“左”、“右”进行比较,若相同才叫对称。
class Solution {
public boolean isSymmetric(TreeNode root) {
return compare(root,root);
}
public boolean compare(TreeNode l,TreeNode r){
if(l==null && r==null) //情况1
return true;
if(l==null || r==null) //情况2
return false;
if(l.val == r.val){
//情况3
return compare(l.left,r.right)&&compare(l.right,r.left);
}
return false; //情况4
}
}
算法二: 迭代
(待完善)
3. 二叉树的最大深度(★)
算法: 深度优先算法(递归实现)
class Solution {
public int maxDepth(TreeNode root) {
if(root==null)
return 0;
int l_len = maxDepth(root.left);
int r_len = maxDepth(root.right);
return Math.max(l_len,r_len)+1;
}
}
4. 将有序数组转换为二叉搜索树
二叉搜索树是指:或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
题目描述:将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
算法: 递归。每一次执行递归方法都返回一个根结点。根结点的值是数组最中间的元素。这样一来数组就被中间元素拆成左右两个数组,分别作为根结点的左右子树。
length | 0 | 1 | 2 | 3 | 4 | …… |
---|---|---|---|---|---|---|
n=length/2 | / | 0 | 0 | 1 | 2 | …… |
有无左右子树? | / | 无子树 | 有左子树 | 有左子树、右子树 | 有左子树、右子树 | 有左子树、右子树 |
注意: 要判断三种情况:
- 如果数组长度为0,那么就没有树结点了,直接返回null
- 判断数组长度是否大于1,因为只有大于1才会有左子树,否则不用进行查找左右子树
- 数组长度大于1之后还需要判断数组长度是否大于2,因为只有大于2才会出现右子树。
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
if(nums.length == 0) //情况1,数组长度为0,返回空
return null;
int m = nums.length/2;
int n = nums[m];
TreeNode root = new TreeNode(n);
if(nums.length>1){
//情况2,数组长度大于1,有左子树
int[] num1 = new int[m];
if(nums.length>2){
//情况3,数组长度大于2,有右子树
int[] num2 = new int[nums.length-m-1];
System.arraycopy(nums,m+1,num2,0,nums.length-m-1); //利用数组拷贝方法将数组拆分成左右两个
root.right = sortedArrayToBST(num2);
}
System.arraycopy(nums,0,num1,0,m);
root.left = sortedArrayToBST(num1);
}
return root; //返回根结点
}
}
5. 平衡二叉树
题目描述:给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
ps:由于本题没有要求需要为二叉搜索树,所以只用考虑子树高度差这一个方面。
算法一: 递归+递归。该类中的两个方法分别都进行递归,其中:
- 第一个方法:递归地判断每个结点的左右子树高度差的绝对值是否不超过1;
- 第二个方法:查询每个结点的深度。
第一个方法在递归的时候会调用第二个方法,连起来就是:每一次拿到一个结点,会出现三种情况:
- 如果结点为空直接返回true,满足平衡二叉树要求
- 结点不为空的话我就去计算它的左右子树分别的高度(即深度),于是调用第二个方法(即第3题求二叉树深度的方法)。如果两个子树高度差的绝对值大于1,那么直接返回false,不满足平衡二叉树。
- 如果高度差的绝对值小于等于1,则说明该结点满足平衡二叉树,那么就再去看剩下的结点是否满足要求,即:把该结点的左右结点分别作为参数来递归。
因为只有当每个结点都满足它的子树的高度差绝对值不大于1,才能算是平衡二叉树,所以对每一个结点都得查它们左右子树的高度。
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null) //情况1
return true;
int x = Math.abs(TreeDepth(root.left)-TreeDepth(root.right));
if(x>1) //情况2
return false;
//情况3
return isBalanced(root.left)&&isBalanced(root.right);
}
public int TreeDepth(TreeNode root){
if(root==null)
return 0;
int l=TreeDepth(root.left);
int r=TreeDepth(root.right);
return Math.max(l,r)+1;
}
}
算法二:
(待完善)
6. 二叉树的最小深度
算法: 递归
题目描述:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
本题的思想与第3题二叉树的最大深度几乎完全相同,但有些区别,区别在于:如果左右子树只有一棵的结点为null,该怎么考虑?
- 在求二叉树的最大深度中,我们对于null结点直接返回为0,若左右子树只有一个为null的话就直接用
Math.max()
方法找不为空的子树高度,不用去管null结点了,因为它肯定最小,被淘汰了。 - 但是这道题说的是“从根节点到最近叶子节点”,就不能直接用
Math.min()
方法取最小值,因为这样得到的子树最小高度一定是0,但实际上null结点不能算作叶子节点,我需要去看不为空的那一棵树,它才是作为根结点的叶子节点的。也就是说如果根结点只有左子树或者只有右子树,我就不能直接取最小值(0),而应该取不为空的子树高度。因此对于子树返回的高度要考虑四种情况:
- 左子树高度为0,右子树高度不为0:返回右子树高度+1
- 左子树高度不为0,右子树高度为0:返回左子树高度+1
- 左右子树高度均不为0:返回二者中最小值+1
- 左右子树高度均为0:返回0+1=1
这里“+1”的意思是加上根结点的高度。情况3和情况4可以合并为一种情况,都可以是“最小值+1”,因为0和0取最小值也是0.
class Solution {
public int minDepth(TreeNode root) {
if(root == null)
return 0;
int l = minDepth(root.left);
int r = minDepth(root.right);
if(l==0 && r!=0) //情况1
return r+1;
if(l!=0 && r==0) //情况2
return l+1;
else //情况3和4
return Math.min(l,r)+1;
}
}
7. 路径总和
题目描述:给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
算法: 递归。题目要求路径必须为根结点到叶子节点,那么最终的结束点一定归结于叶子节点,换句话说就不能在中途停下来,即使可能从根结点到某个中间结点的值加起来等于目标值,也不能算最终路径,因为路径的结尾必须归结到叶子节点。这就说明了本题的解题思路:递归该方法,每一次拿到一个结点和一个目标值,有三种情况:
- 该结点为空,说明到结尾了都没找到符合要求的路径,直接返回false
- 该结点的左右两个子结点均为空,说明它是叶子节点,那么判断结点值是否与sum相等,相等则返回true,否则返回false
- 若不满足子结点为空,说明它不是叶子节点,那就继续往下走,去看它的左右结点是否有符合要求的路径,但这时目标值要改为
sum = sum - root.val;
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if(root==null) //情况1
return false;
if(root.left==null && root.right==null){
//情况2,若左右结点为空则说明该结点为叶子节点
if(root.val == sum)
return true;
else return false;
}
//情况3
sum = sum - root.val;
return hasPathSum(root.left,sum)||hasPathSum(root.right,sum);
}
}
8. 二叉树的镜像(★★)
题目描述:请完成一个函数,输入一个二叉树,该函数输出它的镜像。
算法: 递归。每次将root结点的左右结点交换位置,再把左右结点作为root递归。
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null)
return root;
TreeNode tem;
tem = root.left; //将左右结点交换位置
root