这个系列主要是记录我刷题的过程。重点是每一类型题解法的循序渐进,按着这个顺序基本每一题都能做出来。而不是某一题的解法,所以适合打算大量刷题的人参考。二叉树相关的刷题顺序是参考 代码随想录。
下面几道还是跟二叉搜索树相关的题目。
二叉搜索树
701. 二叉搜索树中的插入操作
难度中等159
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
示例 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和10^4之间 - 每个节点都有一个唯一整数值,取值范围从
0到10^8 -10^8 <= val <= 10^8- 新值和原始二叉搜索树中的任意节点值都不同
思路
题目提示中:新值和原始二叉搜索树中的任意节点值都不同。这样在不改动原有的树结构的情况下,只需要将小于当前节点的值放左边,大于当前节点的放右边,同时只有当前节点是左右有一个节点为空时,才能插入节点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
//此时可以返回新节点
if(root==null)return new TreeNode(val);
//往左边插入
if(root.val>val){
TreeNode left = insertIntoBST(root.left,val);
if(left!=null) root.left=left;
}
//往右边插入
if(root.val<val){
TreeNode right=insertIntoBST(root.right,val);
if(right!=null) root.right=right;
}
return root;
}
}
450. 删除二叉搜索树中的节点
难度中等395
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
- 首先找到需要删除的节点;
- 如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。
示例:
root = [5,3,6,2,4,null,7]
key = 3
5
/ \
3 6
/ \ \
2 4 7
给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
5
/ \
4 6
/ \
2 7
另一个正确答案是 [5,2,6,null,4,null,7]。
5
/ \
2 6
\ \
4 7
思路
这题大思路不难,很多细节。
- 找到目标节点(递归,前序遍历)
- 将目前节点的前置节点作为新的根节点(使用后置节点也是可以的)
- 删除目标节点
细节在源码备注中:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root==null)return null;
//找到了要删除的结点
if (root.val == key) {
//目标节点有左右子节点
if(root.left!=null&&root.right!=null){
//找到目标节点的前置接节点
if(root.left.right!=null){
TreeNode node =root.left.right;
TreeNode preNode =root.left;
//不停的找右节点
while (node.right!=null){
preNode=node;
node=node.right;
}
//前置节点的左节点不为空,
if(node.left!=null){
preNode.right=node.left;
node.left =root.left;
node.right = root.right;
return node;
}else{
preNode.right = null;
node.left =root.left;
node.right = root.right;
return node;
}
}else{
//如果前置节点就是该节点的左节点,直接用该左节点作为根节点
root.left.right=root.right;
TreeNode newNode =root.left;
root=null;
return newNode;
}
}
//需要返回一个 删除root后,重新构建为BST的root节点
if(root.left==null&&root.right==null){
//目标节点是叶子节点,直接删除即可
return root=null;
}
//返回不为null的子节点
return root.left!=null?root.left:root.right;
}
if (root.val > key) {
root.left= deleteNode(root.left, key);
}
if (root.val < key) {
root.right = deleteNode(root.right, key);
}
return root;
}
}
669. 修剪二叉搜索树
难度中等351
给你二叉搜索树的根节点 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]
示例 3:
输入:root = [1], low = 1, high = 2
输出:[1]
示例 4:
输入:root = [1,null,2], low = 1, high = 3
输出:[1,null,2]
示例 5:
输入:root = [1,null,2], low = 2, high = 4
输出:[2]
提示:
- 树中节点数在范围
[1, 104]内 0 <= Node.val <= 104- 树中每个节点的值都是唯一的
- 题目数据保证输入是一棵有效的二叉搜索树
0 <= low <= high <= 104
思路
还是得用递归,从上往下前序遍历。如果不符合[L,R],删除即可。
需要注意的一个节点的值小于L,但是它的右节点是可能大约等于L的。
同理,某节点的值大于R,但是它的左节点可能小于等于R的。这类节点不能都删除了
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root==null) return null;
if(root.val<low){
//该节点的右节点可能>=low
return trimBST(root.right,low,high);
}
if(root.val>high){
//该节点的左子节点坑你<=heig
return trimBST(root.left,low,high);
}
root.left = trimBST(root.left,low,high);
root.right = trimBST(root.right,low,high);
return root;
}
}
108. 将有序数组转换为二叉搜索树
难度简单701
给你一个整数数组 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] <= 104nums按 严格递增 顺序排列
思路
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return sortedArrayToBST(0,nums.length-1,nums);
}
//[start,end]
private TreeNode sortedArrayToBST(int start,int end,int[] nums){
if(start>end) return null;
//中间 注意+start
int mid = start+((end-start)>>1);
TreeNode root=new TreeNode(nums[mid]);
root.left = sortedArrayToBST(start,mid-1,nums);
root.right = sortedArrayToBST(mid+1,end,nums);
return root;
}
}
538. 把二叉搜索树转换为累加树
难度中等478
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
**注意:**本题和 1038: leetcode-cn.com/problems/bi… 相同
示例 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之间。 - 树中的所有值 互不相同 。
- 给定的树为二叉搜索树。
思路
这道题看上去挺唬人的。不过只要搞清楚什么是累加树,已经累加树的值是怎么计算得来的,就很简单了。
如下图,按照 右-->中-->左的顺序 累加即可:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//记录前一个节点的值
int preValue =0;
public TreeNode convertBST(TreeNode root) {
//什么是累加树?
//累加树的值是怎么计算的?
if(root==null) return null;
convertBST(root.right);
root.val+=preValue;
preValue=root.val;
convertBST(root.left);
return root;
}
}
99. 恢复二叉搜索树
难度困难415
给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
**进阶:**使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?
示例 1:
输入:root = [1,3,null,null,2]
输出:[3,1,null,null,2]
解释:3 不能是 1 左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。
示例 2:
输入:root = [3,1,4,null,null,2]
输出:[2,1,4,null,null,3]
解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 2 和 3 使二叉搜索树有效。
提示:
- 树上节点的数目在范围
[2, 1000]内 -231 <= Node.val <= 231 - 1
思路
首先,得知道 二叉搜索树中序遍历会是个升序。
如果该树是有问题的,那么这个升序中就是有 逆序对。而且题目中指出只有一对逆序对。
所以值需要找到逆序对,然后交换值即可:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//上一次中序遍历的结点
private TreeNode prev;
//第一个错误结点
private TreeNode first;
//第二个错误结点
private TreeNode second;
public void recoverTree(TreeNode root) {
findWrongNodes(root);
//交换两个错误结点的值
int temp=second.val;
second.val=first.val;
first.val=temp;
}
private void findWrongNodes(TreeNode root){
if(root==null) return;
//注意是中序遍历
findWrongNodes(root.left);
if(prev!=null&&prev.val>root.val){
//出现逆序对
//第二错误结点,是最后一个逆序对中较小的那个结点
second=root;
//第一个错误结点,是第一个逆序对中较大的那个结点
if(first!=null) return;
first=prev;
}
prev=root;
findWrongNodes(root.right);
}
}
333. 最大 BST 子树
难度中等81
给定一个二叉树,找到其中最大的二叉搜索树(BST)子树,并返回该子树的大小。其中,最大指的是子树节点数最多的。
**二叉搜索树(BST)**中的所有节点都具备以下属性:
- 左子树的值小于其父(根)节点的值。
- 右子树的值大于其父(根)节点的值。
注意:
- 子树必须包含其所有后代。
进阶:
-
你能想出 O(n) 时间复杂度的解法吗?
示例 1:
输入:root = [10,5,15,1,8,null,7]
输出:3
解释:本例中最大的 BST 子树是高亮显示的子树。返回值是子树的大小,即 3 。
示例 2:
输入:root = [4,2,7,2,3,5,null,2,null,null,null,null,null,1]
输出:2
提示:
- 树上节点数目的范围是
[0, 104] -104 <= Node.val <= 104
思路
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int largestBSTSubtree(TreeNode root) {
return (root==null)?0:getInfo(root).size;
}
//获取当前node为根节点的二叉树的最大BST子树的信息
private Info getInfo(TreeNode node){
if(node==null)return null;
//左子树最大BST信息
Info li=getInfo(node.left);
//右子树最大BST信息
Info ri=getInfo(node.right);
int leftBstSize=-1,rightBstSize=-1,min=node.val,max=node.val;
//左子树没有BST,或者左子树就是BST
if(li==null){
leftBstSize=0;
}else if(li.root==node.left&&node.val>li.max){
leftBstSize=li.size;
min=li.min;
}
//右子树没有BST,或者右子树就是BST
if(ri==null){
rightBstSize=0;
}else if(ri.root==node.right&&node.val<ri.min){
rightBstSize=ri.size;
max=ri.max;
}
//以node为根节点的二叉树是BST
if(leftBstSize>=0&&rightBstSize>=0){
return new Info(node,1+leftBstSize+rightBstSize,min,max);
}
//以node为根节点的二叉树不是BST
if(li!=null&&ri!=null)return (li.size>ri.size)?li:ri;
return(li!=null)?li:ri;
}
private static class Info{
public TreeNode root;
public int size;
public int min;
public int max;
public Info(TreeNode root,int size,int min,int max){
this.root= root;
this.size=size;
this.min=min;
this.max=max;
}
}
}
最小公共区域
235. 二叉搜索树的最近公共祖先
难度简单533
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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 为不同节点且均存在于给定的二叉搜索树中。
思路
首先得知道二叉树搜索树的特性,当前节点的左子树节点都比它小,右子树的节点都比它大。依据这个规律,递归即可解:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) return root;
//当前值大于p和q 那么公共祖先肯定在当前节点的左子树
if (root.val > p.val && root.val > q.val) {
//left
TreeNode left = lowestCommonAncestor(root.left, p, q);
if (left != null) return left;
}
//当前值小于p和q 那么公共祖先肯定在当前节点的右子树
if (root.val < p.val && root.val < q.val) {
//right
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (right != null) return right;
}
//p和q一个在大于root一个小于root,就是一个在在root左 一个在root右边。那么root就是他们公共祖先
return root;
}
}
236. 二叉树的最近公共祖先
难度中等947
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2
输出:1
提示:
- 树中节点数目在范围
[2, 105]内。 -109 <= Node.val <= 109- 所有
Node.val互不相同。 p != qp和q均存在于给定的二叉树中。
思路
上面这题,要找到公共祖先,需要从下往上找。首选想到的肯定是深度优先,由于是找公共祖先,根节点肯定是最后遍历到,所以这时候用 后序遍历更合适。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//递归的退出条件,找到了p或者找到了q,或者没有节点时返回。
if(root==p||root==q||root==null) return root;
//后序
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
//如果 p、q一个在左 一个在右,那么root肯定就是他们最近的公共祖先
if(right!=null&&left!=null) return root;
return (left!=null)? left:right;
}
}
1257. 最小公共区域
难度中等
给你一些区域列表 regions ,每个列表的第一个区域都包含这个列表内所有其他区域。
很自然地,如果区域 X 包含区域 Y ,那么区域 X 比区域 Y 大。
给定两个区域 region1 和 region2 ,找到同时包含这两个区域的 最小 区域。
如果区域列表中 r1 包含 r2 和 r3 ,那么数据保证 r2 不会包含 r3 。
数据同样保证最小公共区域一定存在。
示例 1:
输入:
regions = [["Earth","North America","South America"],
["North America","United States","Canada"],
["United States","New York","Boston"],
["Canada","Ontario","Quebec"],
["South America","Brazil"]],
region1 = "Quebec",
region2 = "New York"
输出:"North America"
提示:
2 <= regions.length <= 10^4region1 != region2- 所有字符串只包含英文字母和空格,且最多只有 20 个字母
思路
本题本质也是找 最近公共祖先,不过首先得将数组关系转换成二叉树关系:
class Solution {
private RegionTreeNode root;
private Map<String,RegionTreeNode> m_cache = new HashMap<>();
/**
* 区域树节点类
*/
private static class RegionTreeNode {
String key;// 关键字
RegionTreeNode parent;// 父节点
RegionTreeNode(String key) {
this(key,null);
}
RegionTreeNode(String key, RegionTreeNode parent) {
this.key = key;
this.parent = parent;
}
}
/**
* 构建树结构
* @param regions
*/
private void buildRegionTree(List<List<String>> regions){
for (List<String> list : regions){
// 获取父节点的key
String parentKey = list.get(0);
if (root == null) {
root = new RegionTreeNode(parentKey);
m_cache.put(parentKey,root);
}
// 第一个节点是父节点
RegionTreeNode parentNode = m_cache.get(parentKey);
for (int i = 1;i<list.size();i++){
String key = list.get(i);
RegionTreeNode node = new RegionTreeNode(key);
m_cache.put(key,node);// 加入缓存
node.parent = parentNode;
}
}
}
public String findSmallestRegion(List<List<String>> regions, String region1, String region2) {
buildRegionTree(regions);
RegionTreeNode node1 = m_cache.get(region1);
RegionTreeNode node2 = m_cache.get(region2);
Set<String> set = new HashSet<>();
while (node1 != null){
set.add(node1.key);
node1 = node1.parent;
}
while (node2 != null){
if (set.contains(node2.key)){
return node2.key;
}
node2 = node2.parent;
}
return null;
}
}