目录
1. T235:二叉搜索树的公共祖先
T235:给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
说明:
-
所有节点的值都是唯一的。
-
p、q 为不同节点且均存在于给定的二叉搜索树中。
S:
1.1 法1、递归法
在上一题:二叉树的公共祖先 基础上,再加一点:当我们从上向下去递归遍历,第一次遇到 cur 节点是数值在[p, q]区间中,那么 cur 就是 p 和 q 的最近公共祖先。
C++:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// if (!root) return root;//属于最后的 else return root
if (root->val > p->val && root->val > q->val) {
return lowestCommonAncestor(root->left, p, q);
} else if (root->val < p->val && root->val < q->val) {
return lowestCommonAncestor(root->right, p, q);
} else return root;
}
Java:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// if (root == null || root == p || root == q) return root;
if (root.val > p.val && root.val > q.val) {
return lowestCommonAncestor(root.left, p, q);
} else if (root.val < p.val && root.val < q.val) {
return lowestCommonAncestor(root.right, p, q);
} else return root;
}
1.2 法2、迭代法
比递归更容易理解~
C++:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// if (!root) return root;
while (root) {
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;
}
return nullptr;//别漏!
}
Java:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
while (root != null) {
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;
}
return null;
}
1.3 总结
对于二叉搜索树的最近祖先问题,其实比普通二叉树要简单得多:
不需要使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。
2. T701:二叉搜索树中的插入操作
T701:给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
提示:
-
树中的节点数将在 [0, 104]的范围内。
-
-108 <= Node.val <= 108
-
所有值 Node.val 是 独一无二 的。
-
-108 <= val <= 108
-
保证 val 在原始BST中不存在。
S:遍历二叉搜索树,找到 空节点 插入元素就可以了
2.1 法1、递归
2.1.1 返回值递归
C++:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root) {
TreeNode* node = new TreeNode(val);//也可以不开辟新内存空间,直接root=
return node;
}
//实际上只有当遇到了空节点的时候才会重(补)构
if (root->val < val) root->right = insertIntoBST(root->right, val);
if (root->val > val) root->left = insertIntoBST(root->left, val);//直接else就行
return root;
}
Java:
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
TreeNode node = new TreeNode(val);//也可以不开辟新内存空间,直接root=
return node;
}
if (root.val > val) {
root.left = insertIntoBST(root.left, val);
}
if (root.val < val) {
root.right = insertIntoBST(root.right, val);
}
return root;
}
可以看出代码并不复杂。
2.1.2 无返回值递归
其实递归不需要返回值也可以,那就要不断记录遍历的父节点
C++:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root) {
root = new TreeNode(val);
return root;
}
parent = new TreeNode(0);//可以不要这行
traversal(root, val);
return root;
}
private:
TreeNode* parent = nullptr;// 记录遍历节点的父节点
void traversal(TreeNode* cur, int val) {
if (!cur) {
TreeNode* node = new TreeNode(val);
if (val < parent->val) {
parent->left = node;
} else {
parent->right = node;
}
return;//这行不能少!否则到了下面会空指针异常!
}
parent = cur;
if (cur->val < val) traversal(cur->right, val);
if (cur->val > val) traversal(cur->left, val);
}
Java:
TreeNode parent = null;
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
root = new TreeNode(val);
return root;
}
traversal(root, val);
return root;
}
void traversal(TreeNode cur, int val) {
if (cur == null) {
cur = new TreeNode(val);
if (val > parent.val) {
parent.right = cur;
} else {
parent.left = cur;
}
return;//没有这一行就会栈溢出!
}
parent = cur;
if (cur.val > val) {
traversal(cur.left, val);
} else {
traversal(cur.right, val);
}
}
2.2 法2、迭代
其实无返回值递归已经跟迭代有相同的处理逻辑了~
C++:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root) {
root = new TreeNode(val);
return root;
}
TreeNode* cur = root;
TreeNode* parent = root;
while (cur) {
parent = cur;
if (cur->val > val) {
cur = cur->left;
} else {
cur = cur->right;
}
// parent = cur;//写这里就错!
}
TreeNode* node = new TreeNode(val);
if (parent->val > val) {
parent->left = node;
} else {
parent->right = node;
}
return root;
}
Java:
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
root = new TreeNode(val);
return root;
}
TreeNode parent = null;
TreeNode cur = root;
while (cur != null) {
parent = cur;
if (cur.val > val) {
cur = cur.left;
} else if (cur.val < val) {
cur = cur.right;
}
// parent = cur;//写这里导致:NullPointerException: cannot find field "val"
}
TreeNode node = new TreeNode(val);
if (val > parent.val) {
parent.right = node;
} else {
parent.left = node;
}
return root;
}
3. T450:删除二叉搜索树中的节点
T450:给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
-
首先找到需要删除的节点;
-
如果找到了,删除它。
提示:
-
节点数的范围 [0, 104].
-
-105 <= Node.val <= 105
-
节点值唯一
-
root 是合法的二叉搜索树
-
-105 <= key <= 105
进阶: 要求算法时间复杂度为 O(h),h 为树的高度。
S:本题的难点,也就是关键在于把各种情况考虑周全,在遍历BST中每一层时,有以下五种情况:
-
第一种情况:没找到删除的节点,遍历到空节点,也就是找不到符合的节点,那就直接返回了
-
找到删除的节点:
-
第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
-
第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
-
第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
-
🚩第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
-
3.1 法1、递归
3.1.1 for BST
C++:
TreeNode* deleteNode(TreeNode* root, int key) {
// if (!root || root->val == key) return root;//==要分多情况处理
if (!root) return root;//情况一:没找到
if (root->val == key) {
//情况二:目标节点为叶子节点
if (!root->left && !root->right) {
delete root;
return nullptr;
}
//情况三:左子节点为空,右不为空
else if (!root->left && root->right) {
auto retNode = root->right;
delete root;
return retNode;
}
//情况四:左子节点不为空,右为空
else if (root->left && !root->right) {
auto retNode = root->left;
delete root;
return retNode;
}
//🚩情况五:左右子节点均不为空
// else if (root->left && root->right) {//可以不用写明
else {
TreeNode* cur = root->right;
// while (cur)
while (cur->left) {
cur = cur->left;
}
cur->left = root->left;
TreeNode* temp = root;
root = root->right;
delete temp;//这一行和上一行的顺序不能反!
return root;
//下面一段是自以为也对的反向写法,实际上会错。。。
// TreeNode* cur = root->left;
// while (cur->right) {
// cur = cur->right;
// }
// cur->right = root->right;
// TreeNode* temp = root;
// root = root->left;
// delete temp;
// return root;
}
}
if (root->val > key) root->left = deleteNode(root->left, key);
else if (root->val < key) root->right = deleteNode(root->right, key);
return root;
}
Java:
版本Ⅰ、
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return root;
if (root.val == key) {
// if (root.left == null && root.right == null) {
// // root = null;
// return null;
// } else if (root.left == null && root.right != null) {
// root = root.left;
// return root;
// } //反正Java不用考虑释放内存的问题,可以合为下面的一个if(版本Ⅱ中进一步简化)
if (root.left == null) {
return root.right;
}
// else if (root.left != null && root.right == null) {
else if (root.right == null) {
return root.left;
} else {
TreeNode cur = root.right;
while (cur.left != null) {
cur = cur.left;
}
cur.left = root.left;
root = root.right;
return root;
}
}
else if (root.val > key) root.left = deleteNode(root.left, key);
else root.right = deleteNode(root.right, key);
return root;
}
版本Ⅱ、
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return root;
if (root.val > key) {
root.left = deleteNode(root.left, key);
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
} else {
// if (root.left == null) {
// return root.right;
// } else if (root.right == null) {
// return root.left;
// 在版本Ⅰ的基础上再简化为下面一个if
if (root.right == null) {
return root.left;
} else {
TreeNode cur = root.right;
while (cur.left != null) {
cur = cur.left;
}
//和上一种版本的区别就在下面这两行
root.val = cur.val;
// root.right = deleteNode(root.right, key);
root.right = deleteNode(root.right, cur.val);
}
}
return root;
}
3.1.2 for 普通BT
跟上面的版本Ⅱ有些像。。
C++:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root) return root;
if (root->val == key) {
if (!root->right) {
return root->left;
} else {
TreeNode* cur = root->right;
while (cur->left) {
cur = cur->left;
}
swap(root->val, cur->val);
}
}
//下两行不能用else{}
root->left = deleteNode(root->left, key);
root->right = deleteNode(root->right, key);
return root;
}
Java:
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
if (root.right == null) {
return root.left;
} else {
TreeNode cur = root.right;
while (cur.left != null) {
cur = cur.left;
}
root.val ^= cur.val;
cur.val ^= root.val;
root.val ^= cur.val;
// root.right = deleteNode(root.right, key);
}
}
root.left = deleteNode(root.left, key);
root.right = deleteNode(root.right, key);
return root;
}
3.2 法2、迭代法
核心处理逻辑是一样的,只不过迭代法中需要把找节点和删除节点的逻辑明显分开
C++:
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root) return nullptr;
TreeNode* pre = nullptr;
TreeNode* cur = root;
while (cur) {
if (cur->val == key) break;
pre = cur;
// else if (cur->val > key) {
if (cur->val > key) {
cur = cur->left;
} else {
cur = cur->right;
}
}
//到这里,要么已经找到了,要么是确定找不到了(cur = nullptr)
if (!pre) return deleteSingleNode(cur);// 如果搜索树只有头结点
// pre 要知道是删左孩子还是右孩子
if (pre->left && pre->left->val == key) {
pre->left = deleteSingleNode(pre->left);
}
if (pre->right && pre->right->val == key) {
pre->right = deleteSingleNode(pre->right);
}
return root;
}
private:
TreeNode* deleteSingleNode(TreeNode* target) {
if (!target) return nullptr;
if (!target->right) return target->left;
TreeNode* cur = target->right;
while (cur->left) {
cur = cur->left;
}
cur->left = target->left;
// return target->right;//无法delete target
TreeNode* returnNode = target->right;
delete target;
return returnNode;
}
Java:
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
TreeNode cur = root;
TreeNode pre = null;
while (cur != null) {
if (cur.val == key) break;
pre = cur;
if (cur.val > key) {
cur = cur.left;
} else {
cur = cur.right;
}
}
if (pre == null) return deleteSingleNode(cur);
// if (cur != null && pre.left.val == key) {//逻辑上好像一样,但实际上这样写有空指针异常风险
if (pre.left != null && pre.left.val == key) {
pre.left = deleteSingleNode(cur);
}
if (pre.right != null && pre.right.val == key) {
pre.right = deleteSingleNode(cur);
}
return root;
}
private TreeNode deleteSingleNode(TreeNode target) {
if (target == null) return null;
if (target.right == null) return target.left;
else {
TreeNode cur = target.right;
while (cur.left != null) {
cur = cur.left;
}
cur.left = target.left;
return target.right;
}
}