代码随想录算法训练营Day20 | 654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树

文章详细介绍了如何通过递归和迭代方式构建最大二叉树,合并两个二叉树,以及在二叉搜索树中搜索和验证其性质。重点讨论了递归策略和优化,特别是中序遍历验证二叉搜索树的方法。
摘要由CSDN通过智能技术生成

654.最大二叉树

每次都从序列中搜索最大值,以该值为界分割序列进行递归即可。

· 返回值类型:TreeNode*,返回当前子树的节点指针

· 传入参数:

        vector<int> nums:用于构建当前子树的序列数组

· 终止条件:序列为空,返回nullptr表示当前子树为空

· 单层递归逻辑——前序遍历:

        中:从序列中搜索最大值,构造根节点指针

        左:递归构造左子树,传入最大值左侧的序列

        右:递归构造右子树,传入最大值右侧的序列

TreeNode* traversal(vector<int> nums) {
	if (nums.empty())
		return nullptr;
	// 中,获取序列中的最大值,以此构建子树根节点
	// 剪枝,如果数组中只有一个元素则直接返回
	if (nums.size() == 1)
		return new TreeNode(nums[0]);
	auto maxIter = std::max_element(nums.begin(), nums.end());	// 使用了max_element库函数
	TreeNode* root = new TreeNode(*maxIter);
	// 左
	vector<int> left(nums.begin(), maxIter);
	root->left = traversal(left);
	// 右
	vector<int> right(maxIter + 1, nums.end());
	root->right = traversal(right);

	return root;
}

TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
	TreeNode* root = traversal(nums);
	return root;
}

 优化写法:

将传入参数变为代表左右区间的迭代器,从原始数组中切片,这样就不需要每次都创建左右子树的序列数组了

TreeNode* traversal(vector<int>::iterator left, vector<int>::iterator right, vector<int> &nums) {
	if (right - left == 0)
		return nullptr;
	// 中,获取序列中的最大值,以此构建子树根节点
	// 剪枝,如果数组中只有一个元素则直接返回
	if (right - left == 1)
		return new TreeNode(*left);
	auto maxIter = std::max_element(left, right);
	TreeNode* root = new TreeNode(*maxIter);
	// 左
	root->left = traversal(left, maxIter, nums);
	// 右
	root->right = traversal(maxIter + 1, right, nums);

	return root;
}

TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
	TreeNode* root = traversal(nums.begin(), nums.end(), nums);
	return root;
}

617.合并二叉树

对两颗树的遍历保持同步即可,与单颗树没太大不同。

自己的写法,太冗余了,优化空间巨大:

TreeNode* traversal(TreeNode* root1, TreeNode* root2) {
	// 两个树都为空时该节点才为空
	if (!root1 && !root2)
		return nullptr;

	// 中
	int val = 0;
	val += root1 ? root1->val : 0;
	val += root2 ? root2->val : 0;
	TreeNode* root = new TreeNode(val);
	// 左、右
	if (!root1) {
		root->left = traversal(nullptr, root2->left);
		root->right = traversal(nullptr, root2->right);
	}
	else if (!root2) {
		root->left = traversal(root1->left, nullptr);
		root->right = traversal(root1->right, nullptr);
	}
	else {
		root->left = traversal(root1->left, root2->left);
		root->right = traversal(root1->right, root2->right);
	}
	return root;
}

TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
	TreeNode* root = traversal(root1, root2);
	return root;
}

优化的写法:

· 返回值类型:TreeNode*,返回合并完子树的节点指针

· 传入参数:

        TreeNode* root1:树1的指针

        TreeNode* root2:树2的指针

· 终止条件:任意一颗树为空时,直接返回另一颗树的指针(无论另一颗树是否为空逻辑都是正确的)

· 单层递归逻辑——前序遍历:

       需要处理的只有两块子树都不为空的情况(任一为空都会在终止条件处返回)

        中:更新当前子树的值,直接在root1上原地更新即可

        左:递归构造左子树,root1原地更新

        右:递归构造右子树,root1原地更新

TreeNode* traversal(TreeNode* root1, TreeNode* root2) {
	// 某一颗树为空时直接返回另一颗即可
	if (!root1)
		return root2;
	if (!root2)
		return root1;

	// 需要处理的只有两颗树都不为空的情况
	// 只需要在一颗树上原地处理即可,不需要新建节点
	// 中
	root1->val += root2->val;
	// 左
	root1->left = traversal(root1->left, root2->left);
	// 右
	root1->right = traversal(root1->right, root2->right);

	return root1;
}

TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
	TreeNode* root = traversal(root1, root2);
	return root;
}

700.二叉搜索树中的搜索

递归法:

按照二叉搜索树的性质选择一边子树进行递归即可

· 返回值类型:无返回值类型,使用引用(或全局变量)记录结果

· 传入参数:

        TreeNode* cur:当前节点指针

        int& val:搜索的目标值

        TreeNode*& ans:搜索成功时用于存储搜索结果的指针,初始化为nullptr

· 终止条件:当节点为空时返回,当已经搜索成功时也直接剪枝返回

· 单层递归逻辑:

        应用二叉搜索树的特性,而非前中后序遍历

        · 当前节点值等于目标值:记录搜索结果并返回

        · 当前节点值大于目标值:递归访问左子树

        · 当前节点值小于目标值:递归访问右子树

void traversal(TreeNode* cur, int& val, TreeNode*& ans) {
	if (ans || !cur)
		return;

	if (cur->val == val) {
		ans = cur;
		return;
	}
	else if (cur->val > val)
		traversal(cur->left, val, ans);
	else
		traversal(cur->right, val, ans);
}

TreeNode* searchBST(TreeNode* root, int val) {
	TreeNode* ans = nullptr;
	traversal(root, val, ans);
	return ans;
}

 迭代法:

本题迭代法更简单(迭代法更能利用二叉搜索树的特性,每次迭代的目的更明确)

TreeNode* searchBST0(TreeNode* root, int val) {
	while (root) {
		if (root->val == val)
			return root;
		else if (root->val > val)
			root = root->left;
		else
			root = root->right;
	}
	return root;
}

98.验证二叉搜索树

本题的“坑”:子树的所有节点都要大于/小于根节点

开始有发现坑,但跨层操作没写明白。后来想到既然左子树都要小于根节点,那么设置一个最大值阈值,随着左转不断更新该阈值不就可以了吗,同理设置一个最小值阈值,每次右转更新该阈值即可。

自己的思路:

· 返回值类型:bool,返回该子树是否是二叉搜索树的验证结果

· 传入参数:

        TreeNode* cur:当前节点指针

        long maxLimit:最大值阈值,随着左转次数逐渐减小,由于极值测试用例得设置为long

        long minLimit:最小值阈值,随着右转次数逐渐增大,由于极值测试用例得设置为long

· 终止条件:当节点为空时返回true,定义上空节点是一颗二叉搜索树。

· 单层递归逻辑——前序遍历:

        中:判断当前节点值是否小于最大值阈值,且大于最小值阈值,不符合直接返回false

        左:递归验证左子树,传入参数中最大阈值更新为当前节点的值

        右:递归验证右子树,传入参数中最小阈值更新为当前节点的值

bool traversal(TreeNode* cur, long maxLimit, long minLimit) {
	if (!cur)
		return true;
	// 中
	if (!(cur->val < maxLimit && cur->val > minLimit))
		return false;
	// 左
	bool left = traversal(cur->left, cur->val, minLimit);
	// 右
	bool right = traversal(cur->right, maxLimit, cur->val);
	
	return left && right;
}

bool isValidBST(TreeNode* root) {
	// 要设置好初始上下界,设置太低过不了最值测试用例
	bool ans = traversal(root, LONG_MAX, LONG_MIN);
	return ans;
}

 事实上只需要设置一个阈值就行了:

更好的思路:以中序遍历,如果遍历的序列是递增的,那么该树是二叉搜索树

· 返回值类型:bool,返回该子树是否是二叉搜索树的验证结果

· 传入参数:

        TreeNode* cur:当前节点指针

        TreeNode*& pre:上一个遍历到节点的指针,使用引用(或一个全局变量)记录

· 终止条件:当节点为空时返回true,定义上空节点是一颗二叉搜索树。

· 单层递归逻辑——中序遍历

        左:递归验证左子树

        中:判断当前节点值是否大于上一个节点值,不符合返回false,符合的话更新pre

        右:递归验证右子树,此时传入的pre已经指向当前节点了

bool traversal(TreeNode* cur, TreeNode*& pre) {
	if (!cur)
		return true;
	// 左
	bool left = traversal(cur->left, pre);
	// 中,检测当前节点值是否大于上一个节点的值
	// 注意条件是小于等于
	if (pre && cur->val <= pre->val)
		return false;
	else
		// 更新pre
		pre = cur;
	// 右
	bool right = traversal(cur->right, pre);

	return left && right;
}

bool isValidBST(TreeNode* root) {
	long maxVal = LONG_MIN;		// 初始值是一个极小值
	return traversal(root, maxVal);
}

收获 

二叉搜索树的一个重要性质:以中序遍历二叉树,得到的遍历序列一定是单调递增的

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值