代码随想录算法训练营Day21 | 530. 二叉搜索树的最小绝对差、501. 二叉搜索树中的众数、236. 二叉树的最近公共祖先

530. 二叉搜索树的最小绝对差

这题依旧利用二叉搜索树中序遍历是单调递增的性质,只要以中序遍历,对比其相邻节点差值即可。

利用到了双指针技巧,使用一个pre指针记录上一个节点,cur代表当前节点。

TreeNode* pre = nullptr;
int minDiff = 100001;

void traversal(TreeNode* cur) {
	if (!cur)
		return;

	traversal(cur->left);

	if (pre) {
		int diff = abs(cur->val - pre->val);
		minDiff = std::min(diff, minDiff);
	}
	pre = cur;

	traversal(cur->right);

	return;
}

int getMinimumDifference(TreeNode* root) {
	traversal(root);
	return minDiff;
}

 501. 二叉搜索树中的众数

与上题思路差不多,都是利用到了二叉搜索树中序遍历单调递增的性质,以及双指针。

实现上多了点小技巧:使用全局变量分别记录目前出现的最大频率与当前数出现的频率,在当前数频率大于等于最大频率时对结果数组进行更新。

int count = 1;
int maxCount = 1;
TreeNode* pre = nullptr;
vector ans;

// 依然利用二叉搜索树中序遍历后序列递增的特性
void traversal(TreeNode* cur) {
	if (!cur)
		return;
	// 左
	traversal(cur->left);
	// 中
	if (pre) {
		// 与中序上一个节点相同时进行更新
		if (cur->val == pre->val) {
			++count;
			// 当前频率等于最高频率时加入数组
			if (count == maxCount)
				ans.push_back(cur->val);
			// 当前频率大于最高频率时更新最高频率和结果数组
			else if (count > maxCount) {
				maxCount = count;
				ans = { cur->val };
			}
		}
		// 不同时重置当前频率
		else {
			count = 1;
			// 应对最高频率为1的情况
			if (count == maxCount)
				ans.push_back(cur->val);
		}
	}
	else {
		ans.push_back(cur->val);
	}
	pre = cur;
	// 右
	traversal(cur->right);
}

vector findMode(TreeNode* root) {
	traversal(root);
	return ans;
}

 236. 二叉树的最近公共祖先

这题的重点在于做好情况分类

情况1:两节点分别位于公共祖先的两子树上

情况2:节点本身就是目标节点,另一目标节点分布在其子树之一上。

递归写法1:后序遍历,使用两个返回值分布记录左右子树释放出现过目标节点

TreeNode* ans = nullptr;
TreeNode* target1;
TreeNode* target2;

// 递归写法1
std::pair traversal(TreeNode* cur) {
	if (!cur)
		return { false, false };
	if (ans)
		return { true, true };
	// 左
	auto left = traversal(cur->left);
	// 右
	auto right = traversal(cur->right);
	// 中
	std::pair res;
	// 子树之一出现过目标节点 或 节点自身就是目标节点
	res.first = left.first || right.first || cur == target1;
	res.second = left.second || right.second || cur == target2;
	// 只有第一个出现的公共祖先用于更新ans
	if (!ans && res.first && res.second)
		ans = cur;
	return res;
}

// 后序遍历,每个节点都判断其子树是否出现过p和q
TreeNode* lowestCommonAncestor(TreeNode* root) {
	target1 = p;
	target2 = q;
	traversal(root);
	return ans;
}

递归写法2:只需要一个返回值,因为同一个目标值不可能在左右子树同时出现(即当左子树找到p(写法1中第一个bool判断为true)时,右子树必定不会有p(第一个bool只能是false))

· 返回值类型:bool,返回当前子树是否包含任一目标节点

· 传入参数:

        TreeNode* cur:传入当前节点指针

· 终止条件:

        1、当前节点为空节点时返回false

        2、找到结果(ans不为空指针)时返回true(目的是剪枝,返回啥无关紧要)

· 单层递归逻辑——后序遍历:根节点不断收集左右子树的信息

        左:递归判断左子树,接受左子树中目标节点的信息。如果左子树中找到了目标节点,结果为true。

        右:递归判断右子树,接受右子树中目标节点的信息。如果右子树中找到了目标节点,结果为true。

        中:

        对应情况1:左右结果同时为true,两个节点都在其子树中,更新ans。

        对应情况2:节点自身就是目标节点,左右结果任一为true,更新ans。

TreeNode* ans = nullptr;
TreeNode* target1;
TreeNode* target2;

bool traversal(TreeNode* cur) {
	if (!cur)
		return false;
	if (ans)
		return true;
	bool left = traversal(cur->left);
	bool right = traversal(cur->right);

	bool middle = cur == target1 || cur == target2;
	// 只有第一个出现的公共祖先用于更新ans
	if (!ans) {
        // 情况1:两个目标节点都在子树中
		if (left && right)
			ans = cur;
		// 情况2:节点自身就是目标节点,子树之一中有另一目标节点
		else if (middle && (left || right))
			ans = cur;
	}
	return middle || left || right;
}

// 后序遍历,每个节点都判断其子树是否出现过p和q
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
	target1 = p;
	target2 = q;
	traversal(root);
	return ans;
}

递归写法3(比较巧妙,自己估计想不出来):类似冒泡排序,把最近公共祖先不断冒泡传上去

· 返回值类型:TreeNode*,返回最近公共祖先的节点指针

· 传入参数:

        TreeNode* cur:当前节点指针

· 终止条件:

        1、当前节点为空节点时返回空指针

        2、当前节点为任一目标节点时返回自身(开始向上冒泡)

· 单层递归逻辑——后序遍历:根节点不断收集左右子树的信息

        左:递归判断左子树,接受左子树中目标节点的信息。如果左子树中找到了目标节点,那么该节点会被冒泡传上来,没找到时传上来的是nullptr。

        右:递归判断右子树,接受右子树中目标节点的信息。如果右子树中找到了目标节点,那么该节点会被冒泡传上来,没找到时传上来的是nullptr。

        中:

        1:左右子树结果都非空,说明两个目标节点分布在两侧子树,符合情况1,该节点是最近公共祖先,返回该节点(开始向上冒泡)

        2、左右子树结果任一为空,说明一边子树找到了目标节点,返回非空的那一侧(找到的那个目标节点继续向上冒泡)

        3、(那么情况2呢?)情况2实际被终止条件2包含了,情况2下,终止条件2传上去的那个节点即为最近公共祖先,它在向上冒泡的过程不会被更新(因为除它自身外的另一个目标节点在它的下面)。

TreeNode* target1;
TreeNode* target2;

TreeNode* traversal(TreeNode* cur) {
	// 终止条件1:空节点时返回空指针
	if (!cur)
		return nullptr;
	// 终止条件2:该节点等于任一目标节点则返回该节点
	if (cur == target1 || cur == target2)
		return cur;
	// 左
	TreeNode* left = traversal(cur->left);
	// 右
	TreeNode* right = traversal(cur->right);
	// 中(以下三种分支配合终止条件2可以所有情况)
	if (left && right)
		return cur;
	else if (left && !right)
		return left;
	else
		return right;
}

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
	target1 = p;
	target2 = q;
	return traversal(root);
}

你可能感兴趣的:(算法)