递归
- 树是一种递归结构,所以树的问题很多都可以用递归来解决
- 我们用递归解决问题的时候,各个不同的递归分支就可以看成是树的分支,所以可以将递归问题抽象成树的问题
- 递归解法需要明确三个点
- 返回值
- 调用单元做了什么
- 终止条件(base case)
104. 二叉树的最大深度(Easy)
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
解法一:可以使用dfs来解决,明确三个点:
- 返回值:统计出的最大深度
- 调用单元做了什么:当前深度=左右子树的最大深度+1
- 终止条件:递归到空节点
class Solution {
public int maxDepth(TreeNode root) {
//base case
if (root == null){
return 0;
}
//当前深度=左右子树的最大深度+1
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
解法二:除了使用dfs来求最大深度之外,还可以对二叉树进行“按层遍历”(BFS),统计出二叉树的层数,即为最大深度
- 对二叉树进行从左到右的按层遍历,准备一个队列
queue
来存储节点 - 先把根节点存储进去,弹出,层数+1,然后将其左右节点存储进去
- 重复上述过程直到
queue
为空
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
//BFS的层次遍历思想,记录二叉树的层数,
//遍历完,层数即为最大深度
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
int maxDepth = 0;
while (!queue.isEmpty()) {
maxDepth++;
int levelSize = queue.size();
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.pollFirst();
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
return maxDepth;
}
}
110. 平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false 。
解法一:这道题和上道题有点联系,本题是判断一棵二叉树是否为平衡,而判断平衡的条件需要用到深度,所以可以遍历二叉树,用上道题的解法求出根节点左右子树的最大深度进行比较
class Solution {
public boolean isBalanced(TreeNode root) {
//base case
if (root == null){
return true;
}
//判断当前节点所在的二叉树是否平衡
if (Math.abs(maxDepth(root.left) - maxDepth(root.right)) > 1){
return false;
}
//判断当前节点的左右子树是否平衡
return isBalanced(root.left) && isBalanced(root.right);
}
}
解法二:上述解法存在着大量的重复计算过程,比如判断根节点是否平衡的时候,已经计算过左右子树了,但是后面又重复判断了左右子树是否平衡。
- 方法是如果我们发现子树不平衡,则不计算具体的深度,而是直接返回-1
- 那么优化后的方法为:对于每一个节点,我们通过checkDepth方法递归获得左右子树的深度,如果子树是平衡的,则返回真实的深度,若不平衡,直接返回-1
class Solution {
public boolean isBalanced(TreeNode root) {
if (checkDepth(root) == -1){
return false;
}else {
return true;
}
}
private int checkDepth(TreeNode root){
//base case:到达空节点,直接返回0
if (root == null){
return 0;
}
//调用单元
//求出左右子树的最大深度
int left = checkDepth(root.left);
//如果其中一个不平衡,那么整个不平衡,直接返回-1
if (left == -1){
return -1;
}
int right = checkDepth(root.right);
if (right == -1){
return -1;
}
//比较当前节点的左右子树最大深度差
int diff = Math.abs(left - right);
//不平衡的话直接返回-1
if (diff > 1){
return -1;
}else {
//平衡就返回深度
return 1 + Math.max(left, right);
}
}
}
543. 二叉树的直径(Easy)
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。
示例 :
给定二叉树
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。
题解:这道题让我们求最大直径长度,换个说法就是求节点的左右子树深度之和,要找出最长的,只需要遍历每个节点即可
class Solution {
private int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return max;
}
private int depth(TreeNode root) {
//base case
if (root == null) {
return 0;
}
//求出左右子树的深度
int rightDepth = depth(root.right);
int leftDepth = depth(root.left);
//比较原有max 和 节点左右子树深度之和
max = Math.max((rightDepth + leftDepth), max);
//返回当前节点的深度
return Math.max(leftDepth, rightDepth) + 1;
}
}
226. 翻转二叉树(Easy)
翻转一棵二叉树。
示例:
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
递归解法:递归的三个关键点
- 返回值:交换之后的根节点
- 调用单元做了什么:交换 已经交换成功的左右节点
- 终止条件:节点为空
class Solution {
public TreeNode invertTree(TreeNode root) {
//base case
if (root == null){
return null;
}
//将左节点先保存起来
TreeNode left = root.left;
//交换左右节点
root.left = invertTree(root.right);
root.right = invertTree(left);
//返回根节点
return root;
}
}
非递归解法:非递归的方法也不复杂,跟二叉树的层序遍历一样,需要用queue来辅助,先把根节点排入队列中,然后从队中取出来,交换其左右节点,如果存在则分别将左右节点在排入队列中,以此类推直到队列中木有节点了停止循环,返回root即可。
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
//准备一个队列
Queue<TreeNode> queue = new LinkedList<>();
//先将根节点存储进去
((LinkedList<TreeNode>) queue).push(root);
//交换左右节点
while (!queue.isEmpty()) {
TreeNode node = ((LinkedList<TreeNode>) queue).pop();
TreeNode tmp = node.left;
//交换左右节点
node.left = node.right;
node.right = tmp;
//如果左右节点不为空,添加进队列
if (node.left != null){
((LinkedList<TreeNode>) queue).push(node.left);
}
if (node.right != null){
((LinkedList<TreeNode>) queue).push(node.right);
}
}
return root;
}
}
617. 合并二叉树(Easy)
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
示例 1:
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
注意: 合并必须从两个树的根节点开始。
题解:这道题依然是采用递归解法,先明确三个点
- 返回值:归并之后的节点
- 调用单元做了什么:将两节点合并
- 如果只有左节点存在,返回左节点,反之返回右节点
- 都存在就用新节点存储两节点和
- 终止条件:两节点都为空
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
//base case
if (t1 == null && t2 == null) {
return null;
}
//只有一个存在,就返回存在的那个
if (t1 == null) {
return t2;
}
if (t2 == null) {
return t1;
}
//都存在的话用新节点合并
TreeNode node = new TreeNode(t1.val + t2.val);
//继续合并其左右节点
node.left = mergeTrees(t1.left, t2.left);
node.right = mergeTrees(t1.right, t2.right);
return node;
}
}
112. 路径总和(Easy)
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例: 给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
递归解法:这道题让我们判断是否存在一条路径的和等于目标值,很显然可以使用dfs来解决,依然看递归的3个要点
- 终止条件:遍历到为空就结束,因为判断的是根节点到叶子节点的和
- 调用单元做了什么:当前节点的值加上左节点的值或者右节点的值
- 返回值:从当前节点到叶子节点的路径和是否等于目标值
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
//base case 如果递归到空节点都不等于目标值,返回false
if (root == null){
return false;
}
//到叶子节点的时候路径值等于目标值,返回true
if (root.left == null && root.right == null && sum == 0){
return true;
}
//判断当前节点的左右节点的路径和 = 减去节点的值的目标和
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
}
非递归解法:采用先序遍历,左右子结点都需要加上其父结点值,这样当遍历到叶结点时,如果和 sum 相等了,那么就说明一定有一条从 root 过来的路径。
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null){
return false;
}
//使用栈存储当前层的节点
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
//先序遍历二叉树
while (!stack.isEmpty()) {
//弹出一个节点
TreeNode tmp = stack.pop();
//如果是叶子节点,判断路径和与目标值
if (tmp.left == null && tmp.right == null) {
if (tmp.val == sum){
return true;
}
}
//如果左右节点存在,加入栈
if (tmp.left != null){
tmp.left.val += tmp.val;
stack.push(tmp.left);
}
//如果左右节点存在,加入栈
if (tmp.right != null){
tmp.right.val += tmp.val;
stack.push(tmp.right);
}
}
//遍历完都没有找到,返回false
return false;
}
}
437. 路径总和 III(Easy)
给定一个二叉树,它的每个结点都存放着一个整数值。 找出路径和等于给定数值的路径总数。 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过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
递归解法:这道题是上道题的“升级版”,需要求出所有路径和等于目标值的,而且不需要从根节点开始,也不需要到叶子节点,那么很自然地可以想到使用递归解决,我们可以先准备一个求”当前节点的路径和等于目标值的数量的函数“,然后遍历这棵二叉树即可。
class Solution {
public int pathSum(TreeNode root, int sum) {
if (root == null){
return 0;
}
//路径条数等于以当前节点开始的条数加上左右节点的条数
int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
return ret;
}
private int pathSumStartWithRoot(TreeNode root, int sum) {
//如果遍历到空节点,直接返回0
if (root == null) {
return 0;
}
//统计以当前节点开始的路径条数
int ret = 0;
//如果当前节点值等于目标和,+1
if (root.val == sum){
ret++;
}
//往下寻找
ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);
return ret;
}
}
572. 另一个树的子树(Easy)
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
示例 1:
给定的树 s:
3
/ \
4 5
/ \
1 2
给定的树 t:
4
/ \
1 2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。
示例 2:
给定的树 s:
3
/ \
4 5
/ \
1 2
/
0
给定的树 t:
4
/ \
1 2
返回 false。
题解:这道题让我们判断一棵树是否为另外一棵树的子树,子树必须是从某个节点开始,然后到叶子节点的,那么就可以转为比较两棵树是否相同的问题了,遍历其中一棵树s,判断以某个节点开始的树是否等于另外一棵树t。跟上道题一样是双递归函数的解法
class Solution {
public boolean isSubtree(TreeNode s, TreeNode t) {
if (s == null){
return false;
}
//判断以当前节点开始的二叉树与t是否相同,以及其左右节点开始的是否相同
return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
}
private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
//两个都为空,直接返回true
if (t == null && s == null) {
return true;
}
//其中一个为空,返回false
if (t == null || s == null){
return false;
}
//比较当前节点是否相同
if (s.val != t.val) {
return false;
}
//比较其左右子树是否相同
return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}
}
101. 对称二叉树(Easy)
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [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
说明:如果你可以运用递归和迭代两种方法解决这个问题,会很加分。
递归解法:
- 这道题让我们判断一棵树是否镜像对称:即根节点的左子树和右子树是否镜像对称
- 和上道题很类似,上道题是判断是否为子树:即一棵树的子树和另外一课树是否相等
- 我们可以递归比较根节点的左子树和右子树是否镜像“相同”
- 终止条件:两节点都为空,返回true
- 调用单元做了什么:比较两树对称节点的值是否相等
- 返回值:镜像对称的树的根节点
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
//比较左右子树是否镜像对称
return isSymmetric(root.left, root.right);
}
private boolean isSymmetric(TreeNode t1, TreeNode t2) {
//base case:如果能遍历到根节点,说明镜像对称
if (t1 == null && t2 == null) {
return true;
}
//非镜像对称,或者其中一个为null,返回false
if (t1 == null || t2 == null || t1.val != t2.val) {
return false;
}
//比较左右子树是否镜像对称
return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);
}
}
迭代解法:
- 递归解法核心思想和迭代解法是一样的,只是迭代解法需要两个队列来分别存储根节点的左子树和右子树
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
//准备两个队列
Queue<TreeNode> queue1 = new LinkedList<>();
Queue<TreeNode> queue2 = new LinkedList<>();
((LinkedList<TreeNode>) queue1).push(root.left);
((LinkedList<TreeNode>) queue2).push(root.right);
//遍历左子树和右子树
while (!queue1.isEmpty() && !queue2.isEmpty()) {
//弹出节点
TreeNode node1 = ((LinkedList<TreeNode>) queue1).pop();
TreeNode node2 = ((LinkedList<TreeNode>) queue2).pop();
//如果两节点为空,继续
if (node1 == null && node2 == null) {
continue;
}
//其中一个为空,或者值不像等,则非镜像对称
if (node1 == null || node2 == null || node1.val != node2.val) {
return false;
}
//否则,将左子树的左右节点添加进queue1
//将右子树的左右节点添加进queue2
//注意:添加的顺序
((LinkedList<TreeNode>) queue1).push(node1.left);
((LinkedList<TreeNode>) queue1).push(node1.right);
((LinkedList<TreeNode>) queue2).push(node2.right);
((LinkedList<TreeNode>) queue2).push(node2.left);
}
return true;
}
}
111. 二叉树的最小深度(Easy)
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最小深度 2.
题解:
- 这道题让我们求最小深度,典型的DFS问题
- 同样的找3个关键点
- 终止条件:遍历到空,返回0
- 调用单元做了什么:如果当前节点有一个子节点为空,那么对存在的那个节点调用递归函数,并+1,如果子节点都在,那么取小的+1返回
- 返回值:当前节点的最小深度
class Solution {
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
//到达叶子节点,返回1
if (root.left == null && root.right == null) {
return 1;
}
//其中一个子节点不存在,那么最小深度=当前节点(1)+ 不为空的节点的最小深度
int m1 = minDepth(root.right);
int m2 = minDepth(root.left);
if (root.left == null) {
return 1 + m1;
}
if (root.right == null) {
return 1 + m2;
}
//都不为空的话,就取小的+1
return Math.min(m1, m2) + 1;
}
}
404. 左叶子之和(Easy)
计算给定二叉树的所有左叶子之和。
示例:
3
/ \
9 20
/ \
15 7
在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
递归解法:这道题让我们求左叶子节点的和,那么遍历二叉树,遇到为叶子节点加起来即可
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
//base case
if (root == null) {
return 0;
}
//如果当前节点的左节点为叶子节点,将值加起来
if (isLeaf(root.left)) {
return root.left.val + sumOfLeftLeaves(root.right);
}
//返回值:当前节点的左子树的佐子结点和与右子树的左叶子节点和
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
private boolean isLeaf(TreeNode node) {
if (node == null) {
return false;
}
return node.left == null && node.right == null;
}
}
迭代解法:这道题同样可以使用迭代解法解决,采用先序遍历
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
//根节点为空,或者只有根节点,都为0
if (root == null || (root.left == null && root.right == null)) {
return 0;
}
int res = 0;
//准备一个队列
Queue<TreeNode> queue = new LinkedList<>();
((LinkedList<TreeNode>) queue).push(root);
//线序遍历二叉树
while (!queue.isEmpty()) {
TreeNode node = ((LinkedList<TreeNode>) queue).pop();
//如果当前节点为左叶子节点,将值加起来
if (node.left != null && node.left.left == null && node.left.right == null) {
res += node.left.val;
}
//否则,添加进队列
if (node.left != null) {
((LinkedList<TreeNode>) queue).push(node.left);
}
if (node.right != null) {
((LinkedList<TreeNode>) queue).push(node.right);
}
}
return res;
}
}
687. 最长同值路径(Easy)
给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
注意:两个节点之间的路径长度由它们之间的边数表示。
示例 1:
输入:
5
/ \
4 5
/ \ \
1 1 5
输出:
2
示例 2:
输入:
1
/ \
4 5
/ \ \
4 4 5
输出:
2
注意: 给定的二叉树不超过10000个结点。 树的高度不超过1000。
题解:这道题让我们求相同节点的最长路径和,这种求路径的题目一般都可以用递归来解决,对于节点root来说,最长路径只可能是以下3种情况:1.只与左子树有关;2.只与右子树有关;3.与root有关,所以返回值的定义:以该节点为终点的最长路径。老办法,先找出递归的3个关键点
- 终止条件:遍历到空,返回0
- 返回值:以该结点为终点的最长路径长度,最大长度保存在res
- 调用单元做了什么:对当前节点的左右节点调用递归函数,然后看当前结点和其左右子结点之间的关系了,如果其左子结点存在且和当前节点值相同,则left自增1,否则left重置0;同理,如果其右子结点存在且和当前节点值相同,则right自增1,否则right重置0。然后用left+right来更新结果res。
class Solution {
private int path = 0;
public int longestUnivaluePath(TreeNode root) {
helper(root);
return path;
}
private int helper(TreeNode root) {
//base case
if (root == null) {
return 0;
}
//求出以root.left(root.right)为终点的最长路径
int left = helper(root.left);
int right = helper(root.right);
//也许包含node在内的左路径长度和右路径长度
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是两边加起来的长度与path比较
path = Math.max(path, leftPath + rightPath);
//返回值:以root为终点的最长路径
return Math.max(leftPath, rightPath);
}
}
337. 打家劫舍 III(Medium)
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
示例 1:
输入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
输出: 7
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
示例 2:
输入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
题解:这道题参考了labuladong大佬的解法,打家劫舍一共有三道题目,这是第三道,这一系列题目是动态规划题目,关键点在于“状态”和“选择”,这道题的状态是“索引”,选择是“抢或不抢”。
- 终止条件:节点为空,返回0
- 调用单元做了什么:比较抢当前节点和不抢当前节点,选出较大的值
- 返回值:以当前节点为根节点的二叉树能抢到的最大值
class Solution {
HashMap<TreeNode, Integer> memo = new HashMap<>();
public int rob(TreeNode root) {
//base case
if (root == null) {
return 0;
}
//利用备忘录消除重复子问题
if (memo.containsKey(root)) {
return memo.get(root);
}
//抢,接着去下下家
int doIt = root.val
+ (root.left == null ? 0 : rob(root.left.left) + rob(root.left.right))
+ (root.right == null ? 0 : rob(root.right.left) + rob(root.right.right));
//不抢,然后去下家
int notDoIt = rob(root.left) + rob(root.right);
int res = Math.max(doIt, notDoIt);
memo.put(root, res);
return res;
}
}
671. 二叉树中第二小的节点(Easy)
给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么这个节点的值不大于它的子节点的值。
给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。
示例 1:
输入:
2
/ \
2 5
/ \
5 7
输出: 5
说明: 最小的值是 2 ,第二小的值是 5 。
示例 2:
输入:
2
/ \
2 2
输出: -1
说明: 最小的值是 2, 但是不存在第二小的值。
解法一:
- 根节点是肯定存在的,一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。
- 直接上暴力搜索,变量first存储根节点值,second初始化为Integer最大值,开始搜索
- 如果遇到不等于first的,肯定是比first大,更新second
class Solution {
public int findSecondMinimumValue(TreeNode root) {
//根节点值最小
int first = root.val;
int second = Integer.MAX_VALUE;
helper(root, first, second);
//如果没有找到,返回-1
return (second == first || second == Integer.MAX_VALUE) ? -1 : second;
}
private void helper(TreeNode root, int first, int second) {
if (root == null) {
return;
}
//如果当前节点值不等于first且小于second,更新second
if (root.val != first && root.val < second) {
second = root.val;
}
//往下遍历
helper(root.left, first, second);
helper(root.right, first, second);
}
}
解法二:一个节点要么具有 0 个或 2 个子节点,如果有子节点,那么根节点是最小的节点。
- 如果根节点的左节点和右节点都不等于根节点,那么第二小的值就是比较小的那个
- 如果左节点或右节点等于根节点,那么就往下递归查找
- 如果其中一个不存在,返回存在的那个
class Solution {
public int findSecondMinimumValue(TreeNode root) {
//base case
if (root == null) {
return -1;
}
//左右节点为空,返回-1
if (root.left == null && root.right == null) {
return -1;
}
int leftVal = root.left.val;
int rightVal = root.right.val;
//任何一个子节点的值等于根节点,递归查找
if (leftVal == root.val) {
leftVal = findSecondMinimumValue(root.left);
}
if (rightVal == root.val) {
rightVal = findSecondMinimumValue(root.right);
}
//左右节点都不为空且不等于根节点,返回较小值
if (rightVal != -1 && leftVal != -1) {
return Math.min(leftVal, rightVal);
}
//其中一个为空,返回不为空的
if (leftVal != -1) {
return leftVal;
}
return rightVal;
}
}