669.修剪二叉搜索树
给你二叉搜索树的根节点 root
,同时给定最小边界low
和最大边界 high
。通过修剪二叉搜索树,使得所有节点的值在[low, high]
中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
提示:
- 树中节点数在范围
[1, 104]
内 0 <= Node.val <= 104
- 树中每个节点的值都是 唯一 的
- 题目数据保证输入是一棵有效的二叉搜索树
0 <= low <= high <= 104
思路
递归
第一想法是分为两个递归函数,一个判断左边是否小于low,一个判断右边是否大于high。另外还有一种情况,就是根节点的值在范围外,则直接递归左/右子树。
终止条件:节点值大于high或小于low
递归操作:当前节点左右子树进行递归
返回值:当前节点
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root==null){
return null;
}
if(root.val>high){
return trimBST(root.left,low,high);
}
if(root.val<low){
return trimBST(root.right,low,high);
}
root.left=trimLow(root.left,low);
root.right=trimHigh(root.right,high);
return root;
}
public TreeNode trimLow(TreeNode node,int low){
if(node==null){
return null;
}
if (node.val >= low) {
node.left=trimLow(node.left,low);
}else {
node=trimLow(node.right,low);
}
return node;
}
public TreeNode trimHigh(TreeNode node,int high){
if(node==null){
return null;
}
if (node.val <= high) {
node.right=trimHigh(node.right,high);
}else {
node=trimHigh(node.left,high);
}
return node;
}
}
但实际上所有操作都可以在一个函数里实现,代码更加简洁。
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root==null){
return null;
}
if(root.val>high){
return trimBST(root.left,low,high);
}else if(root.val<low){
return trimBST(root.right,low,high);
}else{
root.left=trimBST(root.left,low,high);
root.right=trimBST(root.right,low,high);
return root;
}
}
}
迭代法
分别向左向右迭代,例如往左边迭代时,若当前节点左子节点值小于low,则将左子节点更新为左子节点的右节点。
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
while(root!=null&&(root.val<low||root.val>high)){
if(root.val<low){
root=root.right;
}else{
root=root.left;
}
}
if (root == null) {
return null;
}
for(TreeNode node=root;node.left!=null;){
if (node.left.val < low) {
node.left=node.left.right;
}else {
node=node.left;
}
}
for(TreeNode node=root;node.right!=null;){
if (node.right.val > high) {
node.right=node.right.left;
}else {
node=node.right;
}
}
return root;
}
}
108.将有序数组转换为二叉搜索树
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums
按 严格递增 顺序排列
思路
想要构造一棵二叉树,常见的方法是自顶向下构建,而要从一个严格有序数组构建一个二叉搜索树,我们首先需要找到根节点。若要使最后得出的二叉搜索树是平衡的,根结点应当选择数组中间的树,此时我们默认为中间靠左的节点,因为可能出现节点个数为偶数的情况。
整个递归思路可以视为找到数组某段中可以当作根节点的那个数。所以首先确定root,然后root的左节点为递归root左边部分找到的根节点,右节点为递归root右边部分找到的根节点。
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return sort(nums,0,nums.length-1);
}
public TreeNode sort(int[] nums,int left,int right){
if(left>right){
return null;
}
int mid=(left+right)/2;
TreeNode node=new TreeNode(nums[mid]);
node.left=sort(nums,left,mid-1);
node.right=sort(nums,mid+1,right);
return node;
}
}
538.把二叉搜索树转换为累加树
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node
的新值等于原树中大于或等于 node.val
的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
提示:
- 树中的节点数介于
0
和104
之间。 - 每个节点的值介于
-104
和104
之间。 - 树中的所有值 互不相同 。
- 给定的树为二叉搜索树。
思路
中序遍历/反序中序遍历法
使用两次中序遍历即可完成,第一次中序遍历将所有节点的值相加并存储,第二次中序遍历将总和赋给遍历节点,再将总和减去遍历节点原值,这样的时间复杂度就是O(n),同时因为题目的限制,所有结点的值加起来不会超int限制,所以很方便。
class Solution {
int sum=0;
public TreeNode convertBST(TreeNode root) {
dfs1(root);
dfs2(root);
return root;
}
public void dfs1(TreeNode root){
if(root==null){
return;
}
dfs1(root.left);
sum+=root.val;
dfs1(root.right);
}
public void dfs2(TreeNode root){
if(root==null){
return;
}
dfs2(root.left);
int tmp=root.val;
root.val=sum;
sum-=tmp;
dfs2(root.right);
}
}
但是其实使用了两次中序遍历就已经陷入思维定势了,此处我们可以反序进行中序遍历,记录过程中的节点之和,并更新当前节点值为节点之和,只需遍历一次二叉树。
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
if (root != null) {
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
}
return root;
}
}
Morris遍历
一样的morris,一样的看不懂
主要是利用二叉树中大量的空余空间。
class Solution {
public TreeNode convertBST(TreeNode root) {
int sum = 0;
TreeNode node = root;
//借鉴线索二叉树的思想,充分利用二叉树中的空指针。考虑到遍历顺序为右中左,故当前节点的右子树遍历结束后应当遍历当前节点,故应将当前节点右子树中最后遍历的节点的左指针指向当前节点,
// 即建立右孩子到父节点的联系,而其右子树最后遍历的节点一定为右子树中最左侧节点。
while (node != null){
if (node.right == null){//当前节点没有右孩子,优先遍历当前节点
sum += node.val;
node.val = sum;
node = node.left;//当前节点遍历结束,继续遍历其后继节点
} else {
TreeNode succ = getSuccessor(node);//寻找当前节点的前驱节点
if (succ.left == null){//如果前驱节点左指针为空,将其指向当前节点,方便从当前节点的右子树寻找当前节点
succ.left = node;
node = node.right;//控制权优先转交给其右孩子
} else {//前驱节点左指针不为空,即已经建立线索
succ.left = null;//拆除当前节点的前驱节点的线索还原二叉树(该节点已经在上一次循环中被访问过,其左指针不再需要)
sum += node.val;//遍历当前节点
node.val = sum;
node = node.left;//继续遍历后继节点
}
}
}
return root;
}
//建立一个函数寻找当前节点的前驱节点。
public TreeNode getSuccessor(TreeNode node){
TreeNode succ = node.right;
//前驱节点一定在当前节点右子树的最左端,或前驱节点的左指针已经指向当前节点
while (succ.left != null && succ.left != node){
succ = succ.left;
}
return succ;
}
}
总结
二叉树的部分暂时结束了,递归法的使用越来越熟练,但是迭代法以及morris遍历还是似懂非懂,二刷时继续加强。