淼淼刷力扣

引言:全部算法建立在无重复点的基础上

本人初次尝试写博客,希望各位看官大佬多多包容
有错误希望巨巨们提出来,我一定会及时改正,谢谢大家
在自己最好的年纪,找到了未来的目标
还有1年奋斗刷题,明年去面试实习,加油!

老样子,先看看题目要求:

在这里插入图片描述

简单分划重组二叉树

前序知识:

1、先根遍历每个节点都可以理解为一个根,所以对先序第一个节点理解成根
2、在中序遍历中只要找到根节点的位置就可以将之分为左子树+右子树
前序遍历性质: 节点按照 [ 根节点 | 左子树 | 右子树 ] 排序。
中序遍历性质: 节点按照 [ 左子树 | 根节点 | 右子树 ] 排序。

策略 :建立节点回溯重组

有了前序知识,这题就很简单了,找到出口,把该节点建立,然后把前序和中序进行分解,找到左右子树拼上返回自己即可,所以这明显的回溯模型。出口+左右处理+本节点处理+返回。

具体分析

该算法本质就可以转变成每一次对先序中序的分划,先由先序找到根节点,再在中序分出左右子树,看左子树多长,最后将先序拆分
加粗样式

代码

注:在找范围的时候,可以先根据简单的示例写出来,在不断验证即可。

class Solution {
public:
	TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
	{
		int num_pre = preorder.size();
		int num_in = inorder.size();
		if (num_pre == 0) {//如果分划出的先序或者中序(二者长度一样)为零了代表结束
			return nullptr;
		}
		TreeNode * root = new TreeNode(preorder.front());//先把根拿出来
		int i = 0;
		for (; i < num_in; i++) {//找到中序中根的位置
			if (inorder[i] == preorder.front()) {
				break;
			}
		}
		vector<int> left_inorder, right_inorder, left_preorder, right_preorder;//之后是对先序后续的分割,以下范围可以看上图去理解
		for (int j = 0; j < i; j++) {
			left_inorder.push_back(inorder[j]);
		}
		for (int j = i + 1; j < num_in; j++) {
			right_inorder.push_back(inorder[j]);
		}
		for (int j = 1; j < i + 1; j++) {//每一次小的preorder第一个为根,所以新的从后一个开始
			left_preorder.push_back(preorder[j]);
		}
		for (int j = i + 1; j < num_pre; j++) {
			right_preorder.push_back(preorder[j]);
		}
		TreeNode * left = buildTree(left_preorder, left_inorder);//后根
		TreeNode * right = buildTree(right_preorder, right_inorder);
		root->left = left;//连接
		root->right = right;
		return root;//返回
	}
};

(所有代码均已在力扣上运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):
在这里插入图片描述

(时间复杂度在下方)

时间复杂度: O(N)

总结1

该方法算法层面是正确的,但是可以看到时间耗费上是非常巨大的,无法接受,所以这个算法思想不变,但是实现方法要去优化,下面将介绍优化算法。

优化后的回溯算法

通过观察很容易发现,上面代码时间主要丢失在了1、查找中序中的根节点2、重新组合先序中序队列,所以从这两方面入手优化即可。

优化方案

1、优化查找

1、c++中的map容器,内部采用红黑树实现,内部是有序的,很多操作比如插入删除都是对红黑树的操作,在O(lgn)时间复杂度上就可以完成,记住它内部是严格顺序排列的,利用这个就可以对某些操作加速,并且他的操作都是很快速的,就是浪费空间。
2、本题暂时不用map,因为本体是查找,所以unordered_map,内部是Hash表实现,所以在海量查找有优势,但是建立费时。

2、优化分划

1、我为什么要一次又一次重建先序中序呢,我就保持他俩不变,我就用指针移动模拟不就行了

3、整体优化

1、将关键部分私有化,保障安全性。

代码如下:

注:
1、任何边界的确立:先用简单的样例写,再去用复杂的验证
2、

class Solution {//只能应用于节点不重复的情况下
public:
	TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
	{
		this->preorder = preorder;//初始化
		for (int i = 0; i < inorder.size(); i++) {
			dic[inorder[i]] = i;
		}
		return building(0, 0, preorder.size() - 1, inorder.size() - 1);//调用私有函数得到结果。
	}
private:
	//采用的优化方案就是我把先序中序存起来,利用指针模拟分划即可!!!!!1
	vector<int> preorder;//
	unordered_map<int, int> dic;//哈希map,查找速度快,但是建立较费时,应用于海量数据查找
	//map内部结构规整,利用红黑树实现,具有有序性特点,但是缺点是耗费空间,额外记录父节点孩子节点红黑性质,但操作起来时间复杂度
	//再lgN十分快,还有序
	TreeNode* building(int left_pre, int left_in, int right_pre, int right_in) {
		if (left_pre > right_pre) {
			return nullptr;//设计的出口
		}
		TreeNode* root = new TreeNode(preorder[left_pre]);
		int i = dic[preorder[left_pre]];//利用hashmap可以O(1)查到目标
		int num_left = i - left_in;//我得知道我左子树有多少节点,便于分划先序队列,这个为啥这么减,你用我上面自己画的简单样例模拟一下就行
		root->left = building(left_pre + 1, left_in, left_pre + num_left, i - 1);//左子树部分,中序的右侧明显就是根节点i-1,中序的左侧不用动就行
		//左子树部分,前序的左边界根据定义就是现有跟的后一个节点,右边界就是找到现有根,加上左子树个数就行了
		root->right = building(left_pre + num_left + 1, i + 1, right_pre, right_in);//右子树部分,中序左边界就是跟的右侧i+1,右边界没变
		//右子树部分,先序左边界看左子树右边界加一就行了,因为二者是挨着的,右边界没变
		return root;
	}
};


(所有代码均已在力扣上运行无误)

经测试,该代码运行情况是(经过多次测试所得最短时间):
在这里插入图片描述

(时间复杂度在下方)

时间复杂度: O(N)

Sum Up

1、其实这道题老简单了,就是心中有模型,再加之对先序中序的理解,二者一叠加就能想出来
2、一定要记住优化思路,大量查询unordered_map,变化指针模拟分划,所有范围先看简单的写出来,再用难得验证

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JLU_LYM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值