代码随想录算法训练营第十九天 | 二叉树

48 阅读3分钟

LeetCode # 235. 二叉搜索树的最近公共祖先

📖 考察点

二叉搜索树,从上到下便历,第一个遇到的在区间的节点就是最近公共祖先

📖 题意理解

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

💡 解题思路

从上到下便历。遇到第一个在区间内的节点返回

思路一:

思路二:

🔑 关键点总结

规律总结

💻 代码实现

JavaScript

var lowestCommonAncestor = function (root, p, q) {
	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;
		}
	}
};

⏱️ 复杂度分析

📚 总结与反思


LeetCode 701.二叉搜索树中的插入操作

📖 考察点

二叉搜索树的性质

📖 题意理解

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。

💡 解题思路

利用二叉搜索树的性质,在可以插入的位置如果没有节点,就插入

思路一:

思路二:

🔑 关键点总结

二叉搜索树的性质

💻 代码实现

JavaScript

var insertIntoBST = function (root, val) {
	if (root) {
		return new TreeNode(val);
	}
	let temp = root;
	while (true) {
		if (val > temp.val) {
			if (temp.right) {
				temp = temp.right;
			} else {
				temp.right = new TreeNode(val);
				break;
			}
		}
		if (val < temp.val) {
			if (temp.left) {
				temp = temp.left;
			} else {
				temp.left = new TreeNode(val);
				break;
			}
		}
	}
	return root;
};

⏱️ 复杂度分析

📚 总结与反思


LeetCode 450.删除二叉搜索树中的节点

📖 考察点

二叉树的便历 平衡二叉树

📖 题意理解

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

💡 解题思路

删除节点,保持二叉搜索树的性质 分情况讨论,当找到key时 case1:没有子节点,直接返回null case2:只有单个节点,直接返回子节点 case3:子节点都不为空,此时为了保持二叉搜索树的性质,需要找到左子树的最大值或右子树的最小值,将当前节点的值设为该值,再递归调用函数删除子树中值为该值的元素。

思路一:

思路二:

🔑 关键点总结

二叉树的性质

💻 代码实现

JavaScript

// 1
var deleteNode = function (root, key) {
	if (!root) {
		return root;
	}
	// cur当前节点
	let cur = root;
	// 上一个节点
	let head = new TreeNode(null);
	let pre = head;
	// 方向
	let preDirection = "left";
	pre.left = root;
	while (true) {
		if (key > cur.val) {
			if (cur.right) {
				pre = cur;

				cur = cur.right;
			} else {
				// 不存在 直接结束循环
				break;
			}
		} else if (key < cur.val) {
			if (cur.left) {
				pre = cur;
				preDirection = "left";
				cur = cur.left;
			} else {
				// 不存在,直接结束循环
				break;
			}
		} else {
			// 相等
			if (!cur.left && !cur.right) {
				pre[preDirection] = null;
			} else if (!cur.left) {
				pre[preDirection] = cur.right;
			} else if (!cur.right) {
				pre[preDirection] = cur.left;
			} else {
				// 都有。找左子树最大值 | 右子树最小值
				if (!cur.left.right) {
					cur.val = cur.left.val;
					cur.left = deleteNode(cur.left, cur.left.val);
					break;
				}
				cur = cur.left;
				while (cur.right && cur.right.right) {
					cur = cur.right;
				}
				pre[preDirection].val = cur.right.val;
				cur.right = deleteNode(cur.right, cur.right.val);
			}
			break;
		}
	}
	return head.left;
};


// 2
var deleteNode = function (root, key) {
	if (!root) return null;
	if (key > root.val) {
		root.right = deleteNode(root.right, key);
		return root;
	} else if (key < root.val) {
		root.left = deleteNode(root.left, key);
		return root;
	} else {
		// case 1:节点为叶节点
		if (!root.left && !root.right) {
			return null;
		}
		// case 2:有一个孩子节点不存在
		if (!root.left) {
			return root.right;
		}
		if (!root.right) {
			return root.left;
		}
		// case 3:左右节点都存在
		const rightNode = root.right;
		// 获取最小值节点
		const minNode = getMinNode(rightNode);
		// 将待删节点的值替换为最小值节点值
		root.val = minNode.val;
		root.right = deleteNode(root.right, minNode.val);
		return root;
	}
};
function getMinNode(root) {
	while (root.left) {
		root = root.left;
	}
	return root;
}


⏱️ 复杂度分析

📚 总结与反思