[LeetCode] 235. 二叉搜索树的最近公共祖先 文章解释
[LeetCode] 235. 二叉搜索树的最近公共祖先 视频解释
题目:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 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] 235. 二叉搜索树的最近公共祖先
自己看到题目的第一想法
1. 怎么和昨天的题重复了.
2. 哦, 要利用一下二叉搜索树的特性, 有序的, 递增递减的?
3. 快给我看答案!!!
看完代码随想录之后的想法
1. 神奇的区间法: 如果找到一个节点, 该节点是第一个出现在 [p, q] 区间里的, 则这个节点就是公共祖先.
2. 因为要找第一个位于 [p, q] 区间里的节点, 因此要用先序遍历. 同时当第一个位于 [p, q] 区间里的节点出现时, 有两种情况:
2.1 要么当前节点为 p 或者 q 中的一个, 并且 p 或者 q 中的另一个位于当前节点的某个分支.
2.2 要么 p 或者 q 中的一个位于当前节点的左子树, 另一个位于当前节点的右子树.
3. 不管 2 中当前节点是 2.1 还是 2.2 的情况, 此时节点往左节点或者右节点移动, 移动后一定会覆盖不到之前的某个分支, 导致无法匹配到公共节点.
4. (但是自己真的想不到这点, 还是比较沮丧的.)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
// 无脑递归法: 复习一下(没有限制 p 和 q 必须存在的解法)
// 复习过程 1 遍 AC
class Solution {
private int matchCount = 0;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
TreeNode result = findNode(root, p, q);
if (matchCount == 2) {
return result;
}
return null;
}
private TreeNode findNode(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
TreeNode leftNode = findNode(root.left, p, q);
TreeNode rightNode = findNode(root.right, p, q);
if (leftNode != null && rightNode != null) {
return root;
}
if (root.val == p.val || root.val == q.val) {
matchCount++;
return root;
}
if (leftNode != null) {
return leftNode;
} else if (rightNode != null) {
return rightNode;
} else {
return null;
}
}
}
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
// 递归法: 区间定位法
class Solution {
private int matchCount = 0;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
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;
}
}
}
自己实现过程中遇到哪些困难
无
[LeetCode] 701. 二叉搜索树中的插入操作 文章解释
[LeetCode] 701. 二叉搜索树中的插入操作 视频解释
题目:给定二叉搜索树(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, 10^4]
的范围内。-10^8 <= Node.val <= 10^8
- 所有值
Node.val
是 独一无二 的。-10^8 <= val <= 10^8
- 保证
val
在原始BST中不存在。
[LeetCode] 701. 二叉搜索树中的插入操作
自己看到题目的第一想法
一开始想的很复杂, 首先要从根节点开始查找, 通过递归的方式往左或者往右递归. 然后要判断下一个节点是否大于自己, 是的话就插入到当前节点和下一个节点之间. 否则继续往下遍历. 但是这个想法是错误的. 因为当插入值位于当前节点和下一个节点之间时, 如果插在当前节点和下一个节点之间, 则无法保证下一个节点的子节点都大于当前值, 因此是错误的.
后来在递归的过程中, 产生了比较自然的想法, 根据二叉树的性质, 从跟节点开始, 如果大于根节点就往右遍历, 小于就往左遍历. 如果遇到空, 则说明找到了待添加的位置. 同时表示当前值位于当前子树的最小值的位置. 这才是正确的.
看完代码随想录之后的想法
和自然想法一致. 但是有一点很关键的要注意到: 利用递归的返回值完成回溯, 有时候会有一定的便利性.
// 迭代版: 减少代码行数版
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
return new TreeNode(val);
}
if (root.val > val) {
root.left = insertIntoBST(root.left, val);
} else {
root.right = insertIntoBST(root.right, val);
}
return root;
}
}
// 迭代版: 没有返回值版
class Solution {
private TreeNode parentNode = null;
public TreeNode insertIntoBST(TreeNode root, int val) {
traversal(root, val);
if (parentNode == null) {
return new TreeNode(val);
}
if (parentNode.val > val) {
parentNode.left = new TreeNode(val);
} else {
parentNode.right = new TreeNode(val);
}
return root;
}
private void traversal(TreeNode root, int val) {
if (root == null) {
return;
}
parentNode = root;
if (root.val > val) {
traversal(root.left, val);
} else {
traversal(root.right, val);
}
}
}
// 迭代版本:
class Solution {
private TreeNode parentNode = null;
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) {
return new TreeNode(val);
}
TreeNode node = root;
while (node != null) {
parentNode = node;
if (node.val > val) {
node = node.left;
} else {
node = node.right;
}
}
if (parentNode.val > val) {
parentNode.left = new TreeNode(val);
} else {
parentNode.right = new TreeNode(val);
}
return root;
}
}
自己实现过程中遇到哪些困难
无, 实际上掌握了思想之后, 无论递归法、迭代法、无返回值版, 都变得异常的简单.
[LeetCode] 450. 删除二叉搜索树中的节点 文章说明
[LeetCode] 450. 删除二叉搜索树中的节点 视频说明
题目:
给定一个二叉搜索树的根节点 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
[LeetCode] 450. 删除二叉搜索树中的节点
自己看到题目的第一想法
这题应该没那么难吧, 找到当前节点, 同时记录父节点. 对于搜索二叉树, 左子树需要放到右子树的最左下角的元素下.
写了半天怎么老写不出来呢, 那么多奇怪的变量到底是干嘛的...
看完代码随想录之后的想法
1. 原来挺简单的... 代码很清爽
2. 这里要提一下在 ”往搜索二叉树中插入元素“ 中提到的, 利用递归函数的返回值完成回溯, 有时候会带来一定的便利性.
/**
* 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) {
// 元素左右为空, 说明是叶子结点.
// 对于上一个节点来说, 如果要删除的下一个节点是叶子结点.
// 那么上一个节点的下一个节点就是 null
return null;
} else if (root.left != null && root.right == null) {
// 左子树非空, 右子树为空
return root.left;
} else if (root.left == null && root.right != null) {
return root.right;
} else {
TreeNode node = root.right;
while (node.left != null) {
node = node.left;
}
node.left = root.left;
return root.right;
}
} else if (root.val > key) {
root.left = deleteNode(root.left, key);
} else {
root.right = deleteNode(root.right, key);
}
return root;
}
}
/**
* 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 preNode;
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) {
return null;
}
TreeNode node = root;
while (node != null) {
if (node.val == key) {
break;
}
preNode = node;
if (node.val > key) {
node = node.left;
} else {
node = node.right;
}
}
if (preNode == null) {
root = deleteNode(root);
} else if (preNode.val > key) {
preNode.left = deleteNode(preNode.left);
} else {
preNode.right = deleteNode(preNode.right);
}
return root;
}
private TreeNode deleteNode(TreeNode node) {
if (node == null) {
return null;
}
if (node.left == null) {
return node.right;
} else if (node.right == null) {
return node.left;
} else {
TreeNode temp = node.right;
while (temp.left != null) {
temp = temp.left;
}
temp.left = node.left;
return node.right;
}
}
}
自己实现过程中遇到哪些困难
那可真的是太多了, 慢慢添加的变量, 一堆的变量. 最终演化成很多 case 过不了的垃圾代码.
主要的问题在于没有理顺清楚, 要删除的节点都有哪些情况, 以及没有确定的用递归的思想去解决问题. 这都导致了思绪的杂乱. 左空右空、左非空右空、左空右非空、左右非空、找不到节点, 这五种情况列出来之后, 代码就变得非常的顺畅了.