所用代码 java
二叉搜索树的最近公共祖先 LeetCode 235
题目链接:二叉搜索树的最近公共祖先 LeetCode 235 - 中等
思路
只要昨天的求普通二叉树的最近公共祖先会了,这道题就不难了。重要的是我们如何利用二叉搜索树的性质去寻找最近的公共祖先。
普通二叉树版:后序
- 返回逻辑:root为空,或root找到其中的一个子节点,返回root – 其中一个结点时祖先结点和这种情况一样
- 左右递归
- 中处理逻辑:root的左右子树不为空,返回 root – 证明 左右子树存在p和q,root就为祖先结点
- 一边为空,另一边不为空,返回不会空的结点 – 证明某一条边有p或q
- 都为空,证明孩子无目标结点 return null
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return traversal(root, p, q);
}
public TreeNode traversal(TreeNode root, TreeNode p, TreeNode q){
if (root == null) return null;
// 若其中一个父节点是祖先结点的话,就相当于直接返回祖先
if (root == p || root == q) return root;
TreeNode left = traversal(root.left, p, q);
TreeNode right = traversal(root.right, p, q);
// 中 - 处理逻辑
if (left != null && right != null) return root;
if (left != null && right == null) return left;
else if (left == null && right != null) return right;
else return null;
}
}
利用二叉搜索树的性质:值左小右大
不涉及到遍历顺序,因为二叉树有序,也不需要对二叉树进行处理,只需要有左和右遍历就行了
- 判断root.val与p和q值大小关心
- 根小于root.val就往右递归,大于就往左
- 最后剩下的就是等于,或者两者在中间的情况,此时root就是他们的祖先结点
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return traversal(root, p, q);
}
public TreeNode traversal(TreeNode root, TreeNode p, TreeNode q){
if (root == null) return null;
// 如果p和q的值都小于root,证明都在左边
if (p.val < root.val && q.val < root.val) {
TreeNode left = traversal(root.left, p, q);
if (left != null) return left;
}
// 如果p和q的值都大于root,证明都在右边
if (p.val > root.val && q.val > root.val) {
TreeNode right = traversal(root.right, p, q);
if (right != null) return right;
}
// 若p或q在左右两边 或者等于root
if (p.val <= root.val && q.val >= root.val){
return root;
}
if (p.val >= root.val && q.val <= root.val){
return root;
}
return null;
}
}
上面代码可以简化
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return traversal(root, p, q);
}
public TreeNode traversal(TreeNode root, TreeNode p, TreeNode q){
if (root == null) return null;
// 如果p和q的值都小于root,证明都在左边 -- 左
if (p.val < root.val && q.val < root.val) {
TreeNode left = traversal(root.left, p, q);
if (left != null) return left;
}
// 如果p和q的值都大于root,证明都在右边 -- 右
if (p.val > root.val && q.val > root.val) {
TreeNode right = traversal(root.right, p, q);
if (right != null) return right;
}
// 剩下的情况就是在中间的,或者p或q本身就是root
return root;
}
}
迭代法: 二叉搜索树的迭代法非常简单
while里面要写if else if else才行,因为root进行向下迭代的时候,只会有一种情况
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while (root != null){
// root的值比q和p都大,就往左搜索 ,反之往右
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;
// 否则就是在两边或者p、q其中一个等于root的情况
// 就直接返回root,因为此时root就为祖先
else return root;
}
// 最后就是root=null的情况
return root;
}
}
总结
利用好二叉搜索树的性质,因为他的有序性,递归的时候没有中结点的处理逻辑,迭代的时候也是一步到位。
二叉搜索树中的插入操作 LeetCode 701
题目链接:二叉搜索树中的插入操作 LeetCode 701 - 中等
思路
比较根结点与要插入值的大小关系,判断应该往哪边插入值。
且插入的值都可以在叶子结点插入
。 没有考虑左右是否平衡
- 比较val与根结点(node)值大小关系
- node更小,就把判断node.left是不是为空,为空就把val挂在node的左孩子上,不为空就继续往左搜索
- 往右同理
- 最后会出现val挂在叶子结点上,node.val = val 证明插入成功,就返回
迭代法:
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
TreeNode node = new TreeNode(val);
if (root == null) return node;
// 用一个指针指向插入的位置
TreeNode pre = root;
while (true){
// val值大于根结点的值,就往右
if (pre.val < val && pre.right != null) {
pre = pre.right;
}else if (pre.val < val && pre.right == null){
pre.right = node; // 把值挂在叶子上
// val值小于根结点的值,就往左
}else if (pre.val > val && pre.left != null){
pre = pre.left;
}else if (pre.val > val && pre.left == null){
pre.left = node; // 把值挂在叶子上
}else break; // pre.val = node.val
}
return root;
}
}
迭代还可以双指针,使其操作没那么复杂
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
TreeNode node = new TreeNode(val);
if (root == null) return node;
TreeNode pre = null;
TreeNode cur = root;
// 找到待插入的叶子结点,
// 当cur==null退出,则pre刚好是待插入的叶子结点
while (cur != null){
pre = cur;
if (cur.val > val) cur = cur.left;
else cur = cur.right;
}
// 判断该值的插入位置(左还是右)
if (pre.val > val) pre.left = node;
else pre.right = node;
return root;
}
}
递归法:
- 当我们搜索到空结点,这个空结点就是应该插入的值
- 把这个值返回上去
- 此时根结点的左孩子或者右孩子就把该值给接收了
- 最后返回的root就是构建好的二叉树
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
return insert(root, val);
}
public TreeNode insert(TreeNode root, int val){
// 若遍历到了空结点(叶子结点的下一层),就把node赋值给空结点并返回
if (root == null){
TreeNode node = new TreeNode(val);
// 返回node给root的左或右孩子去接受、相当于插入
return node;
}
// 这里就 root 的左右孩子就用来接收下层传来的已经插入好的二叉树
if (root.val > val){
root.left = insert(root.left, val);
}
if (root.val < val){
root.right = insert(root.right, val);
}
// 最后返回根结点,此时根结点的左右孩子已经完成插入
return root;
}
}
递归还有一种无返回值(双指针)的写法,这种写法就需要定义一个指针指向root的前一个结点
class Solution {
TreeNode pre = new TreeNode(-1);
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) return new TreeNode(val);
insert(root, val);
return root;
}
public void insert(TreeNode cur, int val){
if (cur == null){
TreeNode node = new TreeNode(val);
// pre 跟着cur一路到叶子结点
// 此时pre是叶子结点,cur到了null
// pre就是上一个结点,就用于判断node应该插入在左还是右
if (val > pre.val) pre.right = node;
else pre.left = node;
return;
}
// 用于赋值将pre指向cur已经走过的路
pre = cur;
if (cur.val > val) insert(cur.left, val);
if (cur.val < val) insert(cur.right, val);
return;
}
}
总结
这题我们可以取巧把待插入结点放在叶子结点,就可以递归或者迭代将结点直接插入就可以了,若是不插入在叶子结点,使树为平衡二叉树的话就比较难。
删除二叉搜索树中的节点 LeetCode 450
题目链接:删除二叉搜索树中的节点 LeetCode 450 - 中等
思路
无。
最重要的需清楚删除结点的位置,然后应该怎样删除,据此共有5中情况:
-
没有找到需要删除的结点,返回null
-
待删除的结点:是叶子结点 – 直接把该结点删除,向上返回null
-
左子树为空,右子树不为空 – 将右子树给返回
-
左子树不为空,右子树为空 – 将左子树给返回
-
左右子树都不为空,就需要重新构建二叉树:
- 连接左子树,右子树值都大于左子树,所有把右子树挂在左子树的最右边的结点
- 或者 – 连接右子树,由于左子树的值都小于右子树,所以把左子树的挂在右子树最左边的结点上
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
return delete(root, key);
}
public TreeNode delete(TreeNode root, int key){
// 没找到
if (root == null) return null;
// 找到了的逻辑
if (root.val == key) {
// 第一种情况:删叶子结点
if (root.left == null && root.right == null) return null;
// 第二种情况:左子树为空,右子树不为空,直接将右子树返回
else if (root.left == null && root.right != null) return root.right;
// 第三种情况:左子树不为空,右子树为空,将左子树返回
else if (root.left != null && root.right == null) return root.left;
// 最后一种情况:左右都不为空,删的是中间结点,需重建二叉搜索树
else {
// 以左子树构建 - 找到最右边的结点,并把右子树挂在最后
TreeNode cur = root.left;
while (cur.right != null){
cur = cur.right;
}
cur.right = root.right;
// 返回以左子树新构建的二叉搜索树
return root.left;
}
}
// 二叉搜索树的递归方法,判断往左还是往右
// 根结点的左右子树接收前面递归传回来的已构建好的二叉树
if (root.val > key) root.left = delete(root.left, key);
if (root.val < key) root.right = delete(root.right, key);
return root;
}
}
通用的二叉树删除结点方式:
- 找到待删除结点
- 将他与左或者右叶子结点交换(或者将该叶子结点值赋给该结点)
- 删除叶子结点
if (root.val = key){
if (root.left == null) return right;
if (root.right == null) return left;
// 以往右为例
TreeNode node = root.right;
while (node.right != null){
node = node.right;
}
// node就指向了最右边的叶子结点,再把叶子结点的值赋给该结点
root.val = node.val;
// 再继续向右搜索删除该叶子结点,且删除的是叶子结点的值
root.right = delete(root, node.val);
}
总结
二叉搜索树递归不用分前中后序,因为他本身就可以看作是排好序的二叉树,且中序遍历的值是升序的。
在解决二叉搜索的时候,大体按以下思路操作
public TreeNode traversal(TreeNode root, int key){
// 1、返回逻辑
if (root == null) 返回情况;
其余的返回情况,全部列举
if...
if...
// 2、递归逻辑
// 只用考虑左方向 -- 并接收左孩子传来的结果
if (root.val > key) root.left = traversal(root.left, key);
// 右方向同理
if (root.val < key) root.right = traversal(root.right, key);
// 3、最后递归函数返回值
// 通常返回处理好的结果root
return root;
}