第八节课:

文章目录


题目一

在这里插入图片描述
这么找到搜索二叉树错误的两个节点?正确的搜索二叉树采用中序遍历的结果是一个不降序的序列,题目中说每个节点的值都不一样,因此一定是一个递增的序列。如果交换了其中的两个节点,那么中序遍历的结果中间一定存在降序的情况。如果是原本中序遍历后两个不相邻的节点交换,逆序出现两次,交换的节点为第一次降序的第一个节点和第二次降序的第二个节点;如果是两个相邻的节点交换,逆序只出现一次,出现降序的两个节点都是错误节点。两种情况可以统一为:第一次降序的第一个节点是错误节点、最后一次降序的第二个节点是错误节点。在编程时可以遇到降序都将,降序的后一个节点赋值给第二个错误节点变量;而第一个节点变量则根据是否为空,决定是否赋值,为空则赋值,否则说明已经经历过第一次降序了。
在这里插入图片描述
题目拓展:假设不知道有几个节点错误,但只在两个节点错误时才进行调整,否则不调整。
当存在两个以上降序的时候一定是存在不止两个节点错误,此时不用管了;但是存在一个降序/两个降序也可能不止两个节点错误,比如原本的(1,2,3,4,5)错成了(1,3,4,2,5),2、3、4节点都发生了错误,但是只存在一次降序;再比如原本的(1,2,3,4,5)错成了(2,1,3,5,4),1、2、4、5都发生了错误,但是只存在两次逆序。因此怎么识别一次逆序/两次逆序是否是两个节点错误造成的呢?可以先假定是由两个节点交换造成,尝试将两个节点交换回来,看是否还原成了搜索二叉树。

在这里插入图片描述
当我们获取错误节点之后,不能进行简单的值交换,因为对于一个结构体,其中还有其他的内容,而对应的值只是用来组织二叉树的一个变量,只进行值交换,其他成员是不变的,有点类似掩耳盗铃(虽然在leetcode上测试代码可以通过)
在进行节点交换时需要考虑,1 要交换的节点有没有头节点;2 要交换的节点存不存在父子关系;3 要交换的节点存不存在兄弟关系;4 要交换的节点是其父的左孩子还是右孩子。对于本题而言,由于中序遍历中e0在e1的左边,所以在树中e0也要位于e1的左侧,可以排除掉多种情况。

在这里插入图片描述
在这里插入图片描述
代码实现:

class Node {
public:
	int val;
	Node* left;
	Node* right;
	Node(int val) {
		this->val = val;
		this->left = nullptr;
		this->right = nullptr;
	}
};

//获取错误节点
vector<Node*> getErrorNodes(Node* head) {
	//默认至少有两个节点
	vector<Node*>errs(2, nullptr);
	stack<Node*>sk;
	Node* pre = nullptr;
	while (head != nullptr || !sk.empty()) {
		if (head != nullptr) {
			sk.push(head);
			head = head->left;
		}
		else {
			head = sk.top();
			sk.pop();

			//打印部分
			if (pre != nullptr && pre->val > head->val) {
				errs[0] = (errs[0] == nullptr ? pre : errs[0]);
				errs[1] = head;
			}
			pre = head;

			head = head->right;
		}
	}
	return errs;
}

//获取错误节点的父节点
//如果错误节点是根节点则父节点为空
vector<Node*> getErrorNodeParents(Node* head, Node* e1, Node* e2) {
	Node* h = head;
	vector<Node*>res(2, nullptr);
	stack<Node*>sk;
	while (head != nullptr || !sk.empty()) {
		if (head != nullptr) {
			sk.push(head);
			head = head->left;
		}
		else {
			head = sk.top();
			sk.pop();

			//打印
			if (head->left == e1 || head->right == e1) {
				res[0] = head;
			}
			if (head->left == e2 || head->right == e2) {
				res[1] = head;
			}

			head = head->right;
		}
	}
	return res;
}

void recoverBST(Node* head) {
	vector<Node*>errs = getErrorNodes(head);
	vector<Node*>parents = getErrorNodeParents(head, errs[0], errs[1]);

	//e0和e1的左右孩子
	Node* left0 = errs[0]->left;
	Node* right0 = errs[0]->right;
	Node* left1 = errs[1]->left;
	Node* right1 = errs[1]->right;

	if (parents[0] == nullptr || parents[1] == nullptr) {
		//其中一个错误节点是整棵树的头
		if (parents[0] == nullptr) {
			//e0是整棵树的头节点
			if (errs[0] == parents[1]) {
				//e0是e1的父
				//e1只可能是e0的右孩子,因为中序遍历先遍历到的是e0
				errs[0]->left = left1;
				errs[0]->right = right1;

				errs[1]->left = left0;
				errs[1]->right = errs[0];//这一步是关键
			}
			else {
				//两个节点不相邻
				//e1只可能在e0的右子树上,因为中序遍历先遍历到的是e0
				//if-else中的代码可以整合,这里为了便于理解没有进行整合
				if (parents[1]->left == errs[1]) {
					//e1是其父的左孩子
					errs[0]->left = left1;
					errs[0]->right = right1;
					parents[1]->left = errs[0];

					errs[1]->left = left0;
					errs[1]->right = right0;
					//修改之后e1没有父
				}
				else {
					//e1是其父的右孩子
					errs[0]->left = left1;
					errs[0]->right = right1;
					parents[1]->right = errs[0];

					errs[1]->left = left0;
					errs[1]->right = right0;
					//修改之后e1没有父
				}
			}
		}
		else {
			//e1是整棵树的头节点
			if (errs[1] == parents[0]) {
				//e1是e0的父
				//e0只可能是e1的左孩子,因为中序遍历先遍历到的是e0
				errs[0]->left = errs[1];//这一步是关键
				errs[0]->right = right1;

				errs[1]->left = left0;
				errs[1]->right = right0;//这一步是关键
			}
			else {
				//两个节点不相邻
				//e0只可能在e1的左子树上,因为中序遍历先遍历到的是e0
				//if-else中的代码可以整合,这里为了便于理解没有进行整合
				if (parents[0]->left == errs[0]) {
					//e0是其父的左孩子
					errs[1]->left = left0;
					errs[1]->right = right0;
					parents[0]->left = errs[1];

					errs[0]->left = left1;
					errs[0]->right = right1;
					//修改之后e0没有父
				}
				else {
					//e0是其父的右孩子
					errs[1]->left = left0;
					errs[1]->right = right0;
					parents[0]->right = errs[1];

					errs[0]->left = left1;
					errs[0]->right = right1;
					//修改之后e0没有父
				}
			}
		}
	}
	else {
		//两个错误节点都不是整棵树的头
		if (errs[0] == parents[1]) {
			//e0是e1的父
			//e1只可能是e0的右孩子
			errs[0]->left = left1;
			errs[0]->right = right1;
			errs[1]->right = errs[0];//

			errs[1]->left = left0;//
			errs[1]->right = errs[0];
			if (parents[0]->left == errs[0]) {
				//e0是其父的左孩子
				parents[0]->left = errs[1];
			}
			else {
				//e0是其父的右孩子
				parents[0]->right = errs[1];
			}
		}
		else if (errs[1] == parents[0]) {
			//e1是e0的父
			//e0只可能是e1的左孩子
			errs[1]->left = left0;
			errs[1]->right = right0;
			errs[0]->left = errs[1];//

			errs[0]->right = right1;
			if (parents[1]->left == errs[1]) {
				//e1是其父的左孩子
				parents[1]->left = errs[0];
			}
			else {
				//e1是其父的右孩子
				parents[1]->right = errs[0];
			}
		}
		else if (parents[0] == parents[1]) {
			//两节点的父相同
			errs[0]->left = left1;
			errs[0]->right = right1;

			errs[1]->left = left0;
			errs[1]->right = right0;

			//只可能e1是左孩子,e2是右孩子
			parents[0]->left = errs[1];
			parents[0]->right = errs[0];
		}
		else {
			//两个节点不相邻
			errs[0]->left = left1;
			errs[0]->right = right1;

			errs[1]->left = left0;
			errs[1]->right = right0;

			if (parents[0]->left == errs[0]) {
				//e0是其父的左孩子
				parents[0]->left = errs[1];
			}
			else {
				//e0是其父的右孩子
				parents[0]->right = errs[1];
			}
		}
	}
}

//中序遍历-打印
void printBST(Node* head) {
	if (head == nullptr) {
		return;
	}
	printBST(head->left);
	cout << head->val << " ";
	printBST(head->right);
}

//拓展问题
void adjustBST(Node* head) {
	//默认至少有两个节点
	Node* h = head;
	vector<Node*>errs(2, nullptr);
	stack<Node*>sk;
	Node* pre = nullptr;
	int count = 0;//记录发生了几次逆序
	while (head != nullptr || !sk.empty()) {
		if (head != nullptr) {
			sk.push(head);
			head = head->left;
		}
		else {
			head = sk.top();
			sk.pop();

			//打印部分
			if (pre != nullptr && pre->val > head->val) {
				errs[0] = (errs[0] == nullptr ? pre : errs[0]);
				errs[1] = head;
				count++;
			}
			pre = head;

			head = head->right;
		}
	}

	if (count == 1 || count == 2) {
		swap(errs[0]->val, errs[1]->val);
		vector<Node*>res = getErrorNodes(h);
		if (res[0] != nullptr) {//说明不是两个节点出错,将调整之后的节点在调整回来
			swap(errs[0]->val, errs[1]->val);
		}
	}

}

题目二

在这里插入图片描述
先考虑线段最大重合问题:给出一组线段,返回线段重叠最多的区域,有多少线段重叠(只有一个点重合不算)
在这里插入图片描述

首先将线段按照起始位置排序;准备一个按照线段结尾位置排序的有序表map;遍历排好序的线段,每次将有序表中结尾位置小于等于当前线段开始位置的元素全删掉,然后将当前线段的结尾添加到有序表,此时有序表中的元素个数,就是处理当前线段产生的结果,最终取所有线段产生的结果的最大值。
因为遍历的线段是按照起始位置从小到达排序的,所以先遍历到的线段的起始位置必然比后遍历到的小。将序表中结尾位置小于等于当前线段开始位置的元素全删掉,意味着将与当前线段一定没有重叠区域的线段全部从有序表中删掉了,保留下来的都是之前遍历过并且与当前线段存在重叠区域的线段,也因为线段是按照起始位置从小到达排序的,所以之前删掉的线段,对后续结果没有影响(如果之前不删,后序也会删)。有序表中保留的元素可以理解为是必须以当前线段的起始位置为重叠区域的起点,最多有多少线段重叠。
在这里插入图片描述
有了上述算法原型,现在看原问题:首先重合区域的底边一定是某一矩形的底边;首先按照矩形底边排序;准备一个存放矩形的容器;遍历矩形,将当前遍历到的矩形添加入容器中,并将容器中上边没有当前矩形的下边高的矩形从容器中删除;此时容器中剩下的是底边比当前矩形底边低,但又能贯穿当前矩形底边的矩形;将当前容器中的每个矩形的左右边界看作是一个线段,就可以用上述的算法原型进行求解。

在这里插入图片描述

在这里插入图片描述

很多二维图形问题都是转化成一维的线段求解的



bool cmp(vector<int>& a, vector<int>& b) {
	return a[0] < b[0];
}



//求线段的最大重叠数
int coinSegmentNum(vector<vector<int>>&arr) {
	sort(arr.begin(), arr.end(), cmp);
	multiset<int>st;//不同线段的尾部可能有重复值,这里要用multiset

	int res = 0;
	for (int i = 0; i < arr.size(); i++) {
		for (multiset<int>::iterator it = st.begin(); it != st.end() && *it <= arr[i][0];) {
			it = st.erase(it);
		}
		st.insert(arr[i][1]);
		
		res = max(res, (int)st.size());
	}
	return res;
}


//求矩形的最大重叠数
bool cmp2(vector<int>& a, vector<int>& b) {
	return a[1] < b[1];
}
class Cmp {
public:
	//set按照上边界排序
	bool operator()(const vector<int>& a, const vector<int>& b)const {//后一个const一定要有
		return (a[3] != b[3] ? a[3] < b[3] : a[1] > b[1]);
	}

};
int coinRectNum(vector<vector<int>>&arr) {
	sort(arr.begin(), arr.end(), cmp2);

	set<vector<int>,Cmp>st;

	int res = 0;
	for (int i = 0; i < arr.size(); i++) {
		for (set<vector<int>, Cmp>::iterator it = st.begin(); it != st.end() && (*it)[3] <= arr[i][1];) {
			it = st.erase(it);
		}
		st.insert(arr[i]);

		vector<vector<int>>temp;
		for (set<vector<int>, Cmp>::iterator it = st.begin(); it != st.end(); it++) {
			temp.push_back({ (*it)[0],(*it)[2] });
		}
		res = max(res, coinSegmentNum(temp));
	}
	return res;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值