这个专题中的题目是我跟随代码随想录的刷题计划,在LeetCode上做的与二叉搜索树相关的题目,用于加深对二叉搜索树的理解!
下面的内容将会有每一道题目的题意、在代码随想录中对应的参考文章、我的思路以及我所写的Java代码,希望对你有帮助!
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-in-a-binary-search-tree
题意:
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
示例 1:
输入:root = [4,2,7,1,3], val = 2
输出:[2,1,3]
Example 2:
输入:root = [4,2,7,1,3], val = 5
输出:[]
提示:
数中节点数在 [1, 5000] 范围内
1 <= Node.val <= 107
root 是二叉搜索树
1 <= val <= 107
参考文章
思路:
这道题,其实主要是要理解二叉搜索树的概念和特点,然后借助二叉搜索树的特点来写,不论是递归法和迭代法,写出来的代码都会非常的简洁,同时注意这里的迭代法甚至不用像之前其他二叉树遍历时使用栈来进行迭代遍历来得那么麻烦,详情看代码。
递归法 Java代码:
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val == val) return root;
else if (root.val > val) return searchBST(root.left, val);
else return searchBST(root.right, val);
}
}
迭代法 Java代码:
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
while (root != null) {
if (root.val == val) return root;
else if (root.val > val) root = root.left;
else root = root.right;
}
return root;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/validate-binary-search-tree
题意:
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
树中节点数目范围在[1, 104] 内
-231 <= Node.val <= 231 - 1
参考文章
思路:
做这道题之前,首先要理解二叉搜索树的概念与特点。
其次,做这道题的时候容易陷入一个陷阱,就是只单纯地将中间节点与左节点和右节点进行比较,直接返回结果。我第一次写这道题时候的代码是:
class Solution {
public boolean isValidBST(TreeNode root) {
boolean ans = true;
if (root.left != null) ans = ans && root.val > root.left.val && isValidBST(root.left);
if (root.right != null) ans = ans && root.val < root.right.val && isValidBST(root.right);
return ans;
}
}
直接就WA了,解答错误的测试用例是:[5,4,6,null,null,3,7],预期结果是false,而代码返回的结果是true。这个测试用例所形成的树是:
在这个测试用例中,每个节点都满足左节点小于中间节点、右节点大于中间节点,所以代码返回的结果是true,但是实际上这个测试用例并不满足二叉搜索树的定义,节点值为3的节点是不应该出现在这个位置的
递归法:
这道题的第一个做法是,利用二叉搜索树的特点,我们直接使用中序遍历得到二叉搜索树节点的有序序列,判断这个有序序列是否是真的有序即可
以上做法的Java代码:
class Solution {
private List<Integer> list;
private void travel(TreeNode node) {
if (node == null)
return;
travel(node.left);
list.add(node.val);
travel(node.right);
}
public boolean isValidBST(TreeNode root) {
list = new ArrayList<>();
travel(root);
for (int i = 0; i < list.size() - 1; i++) {
if (list.get(i) >= list.get(i + 1))
return false;
}
return true;
}
}
但其实我们也可以不将遍历结果转变为数组进行判断,可以在递归获取遍历序列的过程中直接进行判断是否有序。
以上做法的Java代码:
class Solution {
TreeNode rnNode;//记录当前遍历到的节点
public boolean isValidBST(TreeNode root) {
if (root == null) return true;
boolean left = isValidBST(root.left);
if (rnNode != null && rnNode.val >= root.val) return false;
rnNode = root;
boolean right = isValidBST(root.right);
return left && right;
}
}
迭代法:
迭代法其实就是不用递归方法来遍历,而是改用一个栈来实现中序遍历,即迭代的方式,思路与上面的递归法是一样的,都是在中序遍历的过程中直接进行判断是否有序
以上做法的Java代码:
class Solution {
public boolean isValidBST(TreeNode root) {
TreeNode rnNode = null;// 记录当前中序遍历序列的最后一个节点
TreeNode curNode = root;
Deque<TreeNode> deque = new LinkedList<>();
while (curNode != null || !deque.isEmpty()) {
if (curNode != null) {
deque.push(curNode);
curNode = curNode.left;
} else {
curNode = deque.poll();
if (rnNode != null && curNode.val <= rnNode.val)
return false;
rnNode = curNode;
curNode = curNode.right;
}
}
return true;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst
题意:
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值。
差值是一个正数,其数值等于两值之差的绝对值。
示例 1:
输入:root = [4,2,6,1,3]
输出:1
示例 2:
输入:root = [1,0,48,null,null,12,49]
输出:1
提示:
树中节点的数目范围是 [2, 104]
0 <= Node.val <= 105
参考文章
思路:
这道题与《LeetCode 98 验证二叉搜索树》非常相似。
由于二叉搜索树所具有的特点,将二叉搜索树进行中序遍历后得到的是有序的序列,只要我们在中序遍历的过程中不断记录当前遍历到的最后一个节点,然后将当前节点与遍历到的最后一个节点进行相减得到的差与当前答案进行比较即可。
以下用递归法和迭代法两种方式来实现二叉树的中序遍历
递归法 Java代码:
class Solution {
TreeNode rnNode;
int ans = Integer.MAX_VALUE;
private void travel(TreeNode node) {
if (node == null) return;
travel(node.left);
if (rnNode != null) ans = Math.min(node.val - rnNode.val, ans);
rnNode = node;
travel(node.right);
}
public int getMinimumDifference(TreeNode root) {
travel(root);
return ans;
}
}
迭代法 Java代码:
class Solution {
public int getMinimumDifference(TreeNode root) {
Deque<TreeNode> deque = new LinkedList<>();
TreeNode rnNode = null;
TreeNode curNode = root;
int ans = Integer.MAX_VALUE;
while (curNode != null || !deque.isEmpty()) {
if (curNode != null) {
deque.push(curNode);
curNode = curNode.left;
} else {
curNode = deque.poll();
if (rnNode != null)
ans = Math.min(ans, curNode.val - rnNode.val);
rnNode = curNode;
curNode = curNode.right;
}
}
return ans;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-mode-in-binary-search-tree
题意:
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
结点左子树中所含节点的值 小于等于 当前节点的值
结点右子树中所含节点的值 大于等于 当前节点的值
左子树和右子树都是二叉搜索树
示例 1:
输入:root = [1,null,2,2]
输出:[2]
示例 2:
输入:root = [0]
输出:[0]
提示:
树中节点的数目在范围 [1, 104] 内
-105 <= Node.val <= 105
进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
参考文章
思路:
又是一道二叉搜索树的题。涉及到二叉搜索树,要立刻想到,二叉搜索树的中序遍历序列是有序的。
这道题与《LeetCode 98 验证二叉搜索树》和《LeetCode 530 二叉搜索树的最小绝对差》这两道题非常类似,都是利用二叉搜索树的特点,对二叉搜索树进行中序遍历,当遍历到中间节点的时候就进行操作。
以递归法为例,二叉树中序遍历的代码模板是(这里是C++代码,摘自代码随想录):
void searchBST(TreeNode* cur) {
if (cur == NULL) return ;
searchBST(cur->left); // 左
(处理节点) // 中
searchBST(cur->right); // 右
return ;
}
对于中间节点的处理:
这道题中,我们先设一个变量maxCount来记录当前最大的同一个数的出现次数,然后在遍历过程中不断记录遍历到当前的前一个节点,然后将当前节点与前一个节点比较,分为以下情况:
1、如果前一个节点为null,说明当前节点是遍历的第一个节点,将rnCount设置为1
2、如果当前节点与前一个节点值相同,rnCount++
3、如果当前节点与前一个节点值不同,rnCount=1
则得出代码
if (rnNode == null || rnNode.val != node.val) rnCount = 1;
else rnCount++; // rnNode.val == node.val
然后我们再将rnCount与maxCount进行比较:
1、如果rnCount等于maxCount,则将当前节点值加入结果集中
2、如果rnCount大于maxCount,则说明最大次数要改变了,需要将当前结果集清空,然后再将当前节点值加入结果集中
得出代码
if (rnCount == maxCount) ans.add(node.val);
else if (rnCount > maxCount) {
ans.clear();
ans.add(node.val);
maxCount = rnCount;
}
综上,我们分别使用递归法和迭代法来进行中序遍历,以下为代码:
递归法 Java代码:
class Solution {
TreeNode rnNode;
int maxCount;
int rnCount;
List<Integer> ans;
private void travel(TreeNode node) {
if (node == null) return;
travel(node.left);
if (rnNode == null || rnNode.val != node.val) rnCount = 1;
else rnCount++; // rnNode.val == node.val
if (rnCount == maxCount) ans.add(node.val);
else if (rnCount > maxCount) {
ans.clear();
ans.add(node.val);
maxCount = rnCount;
}
rnNode = node;
travel(node.right);
}
public int[] findMode(TreeNode root) {
ans = new ArrayList<>();
travel(root);
int[] ansArr = new int[ans.size()];
for (int i = 0; i < ans.size(); i++) {
ansArr[i] = ans.get(i);
}
return ansArr;
}
}
迭代法 Java代码:
class Solution {
public int[] findMode(TreeNode root) {
TreeNode rnNode = null;
TreeNode curNode = root;
int maxCount = 0;
int rnCount = 0;
Deque<TreeNode> deque = new LinkedList<>();
List<Integer> ans = new ArrayList<>();
while (curNode != null || !deque.isEmpty()) {
if (curNode != null) {
deque.push(curNode);
curNode = curNode.left;
} else {
curNode = deque.poll();
if (rnNode == null || curNode.val != rnNode.val) rnCount = 1;
else rnCount++;
if (rnCount == maxCount) ans.add(curNode.val);
else if (rnCount > maxCount) {
maxCount = rnCount;
ans.clear();
ans.add(curNode.val);
}
rnNode = curNode;
curNode = curNode.right;
}
}
int[] ansArr = new int[ans.size()];
for (int i = 0; i < ans.size(); i++) {
ansArr[i] = ans.get(i);
}
return ansArr;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree
题意:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出: 6
解释: 节点 2 和节点 8 的最近公共祖先是 6。
示例 2:
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出: 2
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉搜索树中。
参考文章
思路:
之前在《LeetCode 236 二叉树的最近公共祖先》这道题中,由于二叉树不一定是二叉搜索树,所以我们利用了递归回溯的做法。
相比之下,由于本题中的二叉树限定是二叉搜索树,难度直接降低了不少。
既然题干说明了是二叉搜索树,那么我们就要充分利用二叉搜索树。这里要发现,当我们从根节点开始往下遍历时,只要找到第一个节点值在 [ p.val , q.val ] (注意是闭区间) 中的节点,那么该节点就是这两个节点的最近公共祖先。 当发现了这点后,这道题就变得非常简单了!可以通过下图感受一下这个过程(图片转自代码随想录)
同时这样做也不需要进行回溯,只需将找到的第一个符合该特点的节点返回即可,且不论是使用递归法还是迭代法来做,代码也都非常简洁!
递归法 Java代码:
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;
}
}
迭代法 Java代码:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while (true) {
if (root.val > p.val && root.val > q.val)root = root.left;
else if (root.val < p.val && root.val < q.val)root = root.right;
else return root;
}
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/insert-into-a-binary-search-tree
题意:
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 1:
输入:root = [4,2,7,1,3], val = 5
输出:[4,2,7,1,3,5]
解释:另一个满足题目要求可以通过的树是:
示例 2:
输入:root = [40,20,60,10,30,50,70], val = 25
输出:[40,20,60,10,30,50,70,null,null,25]
示例 3:
输入:root = [4,2,7,1,3,null,null,null,null,null,null], val = 5
输出:[4,2,7,1,3,5]
提示:
树中的节点数将在 [0, 104]的范围内。
-108 <= Node.val <= 108
所有值 Node.val 是 独一无二 的。
-108 <= val <= 108
保证 val 在原始BST中不存在。
参考文章
思路:
其实我不太理解为什么这道题被归为了中等难度,挺简单的这题。
只要理解了二叉搜索树的概念,从二叉搜索树的根节点开始搜索,直到找到的位置是空节点,我们就将这个空节点换为带要插入的值的节点即可。
这道题我们分别使用递归法和迭代法两种方法来做。
递归法 Java代码:
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
TreeNode node = new TreeNode(val);
return node;
}
if (root.val > val) root.left = insertIntoBST(root.left, val);
if (root.val < val) root.right = insertIntoBST(root.right, val);
return root;
}
}
迭代法 Java代码:
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) return new TreeNode(val);
TreeNode parent = root;
TreeNode cur = root;
while (cur != null) {
parent = cur;
if (cur.val > val) cur = cur.left;
else if (cur.val < val) cur = cur.right;
}
if (parent.val > val) parent.left = new TreeNode(val);
else if (parent.val < val) parent.right = new TreeNode(val);
return root;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/delete-node-in-a-bst
题意:
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
示例 1:
输入:root = [5,3,6,2,4,null,7], key = 3
输出:[5,4,6,2,null,null,7]
解释:给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
另一个正确答案是 [5,2,6,null,4,null,7]。
示例 2:
输入: root = [5,3,6,2,4,null,7], key = 0
输出: [5,3,6,2,4,null,7]
解释: 二叉树不包含值为 0 的节点
示例 3:
输入: root = [], key = 0
输出: []
提示:
节点数的范围 [0, 104].
-105 <= Node.val <= 105
节点值唯一
root 是合法的二叉搜索树
-105 <= key <= 105
进阶: 要求算法时间复杂度为 O(h),h 为树的高度。
参考文章
思路:
其实删除二叉搜索树节点的操作之前在数据结构实验课上就写过,当时我的做法就是,在树中先找出被删除节点,然后将被删除节点右子树中最左边的节点取代删除节点的位置。依据这个想法我写出了下面的代码:
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
TreeNode parent = null, parent1 = null, deleteNode = null, cur = root;
int leftOrRight = 0;//这个变量用来记录被删节点是父节点的左节点还是右节点,0则代表是根节点,1代表是左节点,2代表是右节点
//先找出要删掉的节点
while (cur != null && cur.val != key) {
parent = cur;
if (cur.val > key) {
cur = cur.left;
leftOrRight = 1;
} else if (cur.val < key) {
cur = cur.right;
leftOrRight = 2;
}
}
if (cur == null) return root;//没有要删除节点
deleteNode = cur;
if (deleteNode.right == null) {//被删除节点没有右子树
if (leftOrRight == 0) return deleteNode.left;
else if (leftOrRight == 1) parent.left = deleteNode.left;
else parent.right = deleteNode.left;
return root;
}
//找出要删除节点的右子树中最靠左的节点
cur = deleteNode.right;
if (cur.left == null) {//要删除节点的右子树没有左儿子
cur.left = deleteNode.left;
if (leftOrRight == 0) return cur;
else if (leftOrRight == 1) parent.left = cur;
else parent.right = cur;
return root;
}
while (cur.left != null) {
parent1 = cur;
cur = cur.left;
}
parent1.left = cur.right;
cur.left = deleteNode.left;
cur.right = deleteNode.right;
if (leftOrRight == 0) return cur;
else if (leftOrRight == 1) parent.left = cur;
else parent.right = cur;
return root;
}
}
以上代码确实是能达到题目要求,但是实在是太冗长繁琐了,因为需要考虑到各种情况。
在我看了参考文章后,思路改为将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左节点的左孩子上,过程可以参阅下面这个图(图片转自代码随想录),然后就将上面的代码进行修改,代码在图片下面:
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
TreeNode parent = null, deleteNode = null, cur = root;
int leftOrRight = 0;//这个变量用来记录被删节点是父节点的左节点还是右节点,0则代表是根节点,1代表是左节点,2代表是右节点
//先找出要删掉的节点
while (cur != null && cur.val != key) {
parent = cur;
if (cur.val > key) {
cur = cur.left;
leftOrRight = 1;
} else if (cur.val < key) {
cur = cur.right;
leftOrRight = 2;
}
}
if (cur == null) return root;//没有要删除节点
deleteNode = cur;
if (deleteNode.right == null) {//被删除节点没有右子树
if (leftOrRight == 0) return deleteNode.left;
else if (leftOrRight == 1) parent.left = deleteNode.left;
else parent.right = deleteNode.left;
return root;
}
//找出要删除节点的右子树中最靠左的节点
cur = deleteNode.right;
while (cur.left != null) cur = cur.left;
cur.left = deleteNode.left;
if (leftOrRight == 0) return deleteNode.right;
else if (leftOrRight == 1) parent.left = deleteNode.right;
else parent.right = deleteNode.right;
return root;
}
}
可见代码是简洁了一些,也减少了一些变量。
然后我在参考文章中也看到了很妙的另一种做法(参考文章中普通二叉树的删除方式部分)。
这个做法的思路是,不断将被删除节点的值与被删除节点的右子树中的最左节点的值进行交换,直到被交换的右子树中的最左节点不具有右子树,这时候直接将该节点的左子树取代这个节点。
详细看代码,注意这个过程可能不止一次值的交换的,且使用了递归的方法,写起来也很简洁。
如果对这个过程不太理解,可以在纸上画一画,就能理解这个思路的精妙之处了。
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
if (root.right == null) return root.left;
TreeNode cur = root.right;
while (cur.left != null) cur = cur.left;
root.val = cur.val;
cur.val = key;
}
root.left = deleteNode(root.left, key);
root.right = deleteNode(root.right, key);
return root;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/trim-a-binary-search-tree
题意:
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
示例 1:
输入:root = [1,0,2], low = 1, high = 2
输出:[1,null,2]
示例 2:
输入:root = [3,0,4,null,2,null,null,1], low = 1, high = 3
输出:[3,2,null,1]
提示:
树中节点数在范围 [1, 104] 内
0 <= Node.val <= 104
树中每个节点的值都是 唯一 的
题目数据保证输入是一棵有效的二叉搜索树
0 <= low <= high <= 104
参考文章
思路:
先说递归法的做法:
递归法,需要先确定递归方法的参数、返回值。在这道题中其实涉及到了二叉树重构的问题,所以借助递归方法返回节点的形式,可以简化代码。
递归法 Java代码:
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if (root == null) return null;
if (root.val < low) return trimBST(root.right, low, high);
if (root.val > high) return trimBST(root.left, low, high);
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
return root;
}
}
下面是迭代法的思路:
首选我们将root调整为在范围中的节点,接着再分别对root的左右子树中不在范围内的节点进行剪切。
那么如何进行剪切呢?
如果是遍历到当前节点,对当前节点是否在范围内进行判断,如果不在范围内,则要舍弃当前节点,这样子做的话,需要记录当前节点的父节点(在别的题目中可能还要记录当前节点是父节点的左节点还是右节点),这样子做会比较麻烦。
那么我们干脆就对当前节点的儿子节点是否需要进行剪切进行判断,如果当前节点的儿子节点不在范围内,我们就直接使用儿子节点的儿子节点来替换当前节点的儿子节点(没想到说起来还有点绕)。这样子做,在剪切操作上就来的方便多了!
迭代法 Java代码:
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
//先调整root到范围内
while (root != null && (root.val < low || root.val > high)) {
if (root.val < low) root = root.right;
else root = root.left;
}
TreeNode cur = root;
//剪掉root左子树中不在范围内的节点
while (cur != null) {
while (cur.left != null && cur.left.val < low) cur.left = cur.left.right;
cur = cur.left;
}
cur = root;
//剪掉root右子树中不在范围内的节点
while (cur != null) {
while (cur.right != null && cur.right.val > high) cur.right = cur.right.left;
cur = cur.right;
}
return root;
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree
题意:
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:
示例 2:
输入:nums = [1,3]
输出:[3,1]
解释:[1,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 按 严格递增 顺序排列
参考文章
思路:
这道题我用递归法来完成。
在递归法中,如果涉及到二叉树节点的删除或增加,那么使用递归方法的返回节点的方式可以使得更加方便以及代码更加简洁。由于这道题涉及到二叉树的构建,也即节点的增加,因此我们也使用递归方法返回节点的方式。
由于题目给的输入数据是已经排好序的,既然如此,那也不必客气,直接在每次递归方法中都选出当前数组中的中间值即可,这样构造出的树也自然就是符合高度平衡的特定。对于数组的切割,使用的也不是每次new一个数组的操作,而是使用每次调用递归方法时传入左右下标的方式来实现。
注意在这道题中使用的是左闭右闭的区间,即范围左右下标的节点都是可以抵达的。
还有需要注意的是,我一开始在取数组中间元素的位置时写的是
int mid = (left + right) >> 1;
虽说这道题能过,但是这个写法,如果left和right值都是大于int最大值一半,操作就会越界,mid取到的就是负数!所以应该改成这样写:
int mid = left + ((right - left) / 2);
本题Java代码:
class Solution {
private TreeNode construct(int[] nums, int left, int right) {
if (left > right) return null;
int mid = left + ((right - left) / 2);
TreeNode node = new TreeNode(nums[mid]);
node.left = construct(nums, left, mid - 1);
node.right = construct(nums, mid + 1, right);
return node;
}
public TreeNode sortedArrayToBST(int[] nums) {
return construct(nums, 0, nums.length - 1);
}
}
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/convert-bst-to-greater-tree
题意:
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
注意:本题和 1038: https://leetcode-cn.com/problems/binary-search-tree-to-greater-sum-tree/ 相同
示例 1:
输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]
示例 2:
输入:root = [0,null,1]
输出:[1,null,1]
示例 3:
输入:root = [1,0,2]
输出:[3,3,2]
示例 4:
输入:root = [3,2,4,1]
输出:[7,9,4,10]
提示:
树中的节点数介于 0 和 104 之间。
每个节点的值介于 -104 和 104 之间。
树中的所有值 互不相同 。
给定的树为二叉搜索树。
参考文章
思路:
代码随想录在《二叉树:总结篇!》里面写到的一个总结很好,我在这里贴一下:
- 涉及到二叉树的构造,无论普通二叉树还是二叉搜索树一定前序,都是先构造中节点。
- 求普通二叉树的属性,一般是后序,一般要通过递归函数的返回值做计算。
- 求二叉搜索树的属性,一定是中序了,要不白瞎了有序性了。
由于本题涉及到二叉搜索树属性的求值,因此使用中序遍历来做,但是本题的中序遍历是特殊的中序遍历。
其实这道题不难,只要理解了思路是很简单的!
提到二叉搜索树,要立刻想到在二叉搜索树上使用中序遍历会得到一个有序的序列,而本题所说的累加(使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。),其实就是将每个节点的值加上在这个有序序列中在当前节点对应的值后面的所有数字的总和。
理解了这个思路后,其实我们对二叉搜索树进行一个逆序的中序遍历(即先遍历右节点,再遍历中间节点,最后遍历左节点),并这个过程使用一个sum变量不断叠加当前值就可以了。
这道题我们分别使用递归法和迭代法来做,都是实现二叉树的中序遍历(逆序的)。
递归法 Java代码:
class Solution {
int sum;
private void search(TreeNode node) {
if (node == null) return;
search(node.right);
node.val += sum;
sum = node.val;
search(node.left);
}
public TreeNode convertBST(TreeNode root) {
search(root);
return root;
}
}
迭代法 Java代码:
class Solution {
public TreeNode convertBST(TreeNode root) {
TreeNode cur = root;
int sum = 0;
Deque<TreeNode> deque = new LinkedList<>();
while (cur != null || !deque.isEmpty()) {
if (cur != null) {
deque.push(cur);
cur = cur.right;
} else {
cur = deque.poll();
cur.val += sum;
sum = cur.val;
cur = cur.left;
}
}
return root;
}
}