leecode 解题总结:99. Recover Binary Search Tree

本文介绍了一种解决二叉查找树中两个错误交换元素的问题的方法。通过中序遍历找到两个逆序的元素并进行交换,恢复树的正确结构。文章详细解释了算法原理及实现步骤。
#include <iostream>
#include <stdio.h>
#include <vector>
#include <stack>
using namespace std;
/*
问题:
Two elements of a binary search tree (BST) are swapped by mistake.

Recover the tree without changing its structure.

Note:
A solution using O(n) space is pretty straight forward. Could you devise a constant space solution?

分析:题目说二叉查找树中某两个元素被错误的交换了,需要在不改变结构的情况下回复这棵树。
举例:
		8
	4		12
  2	   6   14  10
首先要明白这里说的两个元素弄错了,是不是指一个结点下的两个元素被错误交换了,
还是任意两个元素被交换了。因为错误地交换,此时必定不是一颗二叉查找树了。
我们可以中序遍历一把,得到不正确的位置
比如,对下面这棵树中序遍历得到: 2 4 6 8 14 12 10,发现从14 12 10这一段不是升序,而是降序
说明
		8
	4		12
  2	   6   14  10
找到第一个逆序的数14,然后不看14,继续寻找下一个逆序的数是12,找到第二个逆序的数,
但是发现12起始不是逆序的,因为如果
则就是这两个数逆序的。
又比如对下面这棵树中序遍历: 2 10 6 8 4 12 14,第一个逆序的数是10,第二个逆序的数是4
		8
	10		12
  2	   6   4  14

		8
	4		14
  2	   6   10  12
  2 4 6 8 10 14 12

		8
	4		12
  14  6   10  2
  14 4 6 8 19 12 2
总结规律:从前向后,找到第一个逆序的数位置i,满足A[i] > A[i+1];
          从后向前,找到第一个逆序的数位置j,满足A[j] < A[j-1]; 或者替换成 从前向后,找到最后一组逆序数j,A[j] < A[j-1]
		  再交换这两个结点
题目说:可以使用O(n)的空间,明显是使用一个数组
找到两个结点后,如何交换结点呢,直接的交换使可以的,但是需要找到这两个结点的各自父节点,各自孩子结点,进行重新指向。
这样太麻烦,不如直接交换这两个结点内部的数据
第一个结点是当前结点 < 上一个结点中的上一个结点
第二个结点是当前结点 < 上一个结点中的当前结点,如果有多次这样的,保留最后一个

输入:
3
1 2 3

2
0 1
输出:
1 2 3
1 0

关键:
1 //第一个结点是当前结点 < 上一个结点中的上一个结点
//第二个结点是当前结点 < 上一个结点中的当前结点,如果有多次这样的,保留最后一个这样的当前结点
//因此,需要记录上一个结点
2 采用迭代遍历中序来寻找,递归无法确定上一次结点,因此不可以
	//递归寻找中序的上一个结点很难弄,必须用非递归方式,用一个栈来做
	//使得当前结点为根节点,然后寻找最左边的结点,并把最左边的结点压入栈中
	//当找不到最左边的结点时,弹出栈中保存的结点即为最左边结点,然后令当前结点=最左边结点的右孩子,重复上述循环
	//那么上一个结点就是当前结点即可
	void inOrder_iteration(TreeNode* root)
	{
		stack<TreeNode*> nodes;
		//nodes.push(root);//根节点无需先压入,因为后续令当前结点为根节点,会压入
		TreeNode* current = root;
		TreeNode* node;
		TreeNode* previous = NULL;
		TreeNode* resultNode1 = NULL;
		TreeNode* resultNode2 = NULL;
		while(!nodes.empty() || current)
		{
			//当前遍历的结点都是寻找最左边结点的沿途结点,都需要先压入,再寻找
			if(current)
			{
				nodes.push(current);
				current = current->left;//这里current表示的就是当前遍历结点,要先压入,而不是
			}
			//找不到最左边的结点,弹出栈中的结点
			else
			{
				node = nodes.top();//这个是最左边结点
				//比较
				if(previous)
				{
					if(node->val < previous->val)
					{
						//只需要比较一次
						if(!resultNode1)
						{
							resultNode1= previous;
						}
						resultNode2 = node;
					}	
				}
				//更新前一个结点
				previous = node;
				nodes.pop();
				//不需要直接压入,因为压入的判断放在前面了,这里只需要时当前结点=结点的右孩子
				current = node->right;
			}
		}
		//交换结点值
		int temp = resultNode1->val;
		resultNode1->val = resultNode2->val;
		resultNode2->val = temp;
	}
*/

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
	TreeNode():left(NULL), right(NULL){}
};

class Solution {
public:
	//递归寻找中序的上一个结点很难弄,必须用非递归方式,用一个栈来做
	//使得当前结点为根节点,然后寻找最左边的结点,并把最左边的结点压入栈中
	//当找不到最左边的结点时,弹出栈中保存的结点即为最左边结点,然后令当前结点=最左边结点的右孩子,重复上述循环
	//那么上一个结点就是当前结点即可
	void inOrder_iteration(TreeNode* root)
	{
		stack<TreeNode*> nodes;
		//nodes.push(root);//根节点无需先压入,因为后续令当前结点为根节点,会压入
		TreeNode* current = root;
		TreeNode* node;
		TreeNode* previous = NULL;
		TreeNode* resultNode1 = NULL;
		TreeNode* resultNode2 = NULL;
		while(!nodes.empty() || current)
		{
			//当前遍历的结点都是寻找最左边结点的沿途结点,都需要先压入,再寻找
			if(current)
			{
				nodes.push(current);
				current = current->left;//这里current表示的就是当前遍历结点,要先压入,而不是
			}
			//找不到最左边的结点,弹出栈中的结点
			else
			{
				node = nodes.top();//这个是最左边结点
				//比较
				if(previous)
				{
					if(node->val < previous->val)
					{
						//只需要比较一次
						if(!resultNode1)
						{
							resultNode1= previous;
						}
						resultNode2 = node;
					}	
				}
				//更新前一个结点
				previous = node;
				nodes.pop();
				//不需要直接压入,因为压入的判断放在前面了,这里只需要时当前结点=结点的右孩子
				current = node->right;
			}
		}
		//交换结点值
		int temp = resultNode1->val;
		resultNode1->val = resultNode2->val;
		resultNode2->val = temp;
	}

	void inOrder(TreeNode* root, TreeNode* previousNode, TreeNode* resultNode1 , TreeNode* resultNode2)
	{
		//空指针就不需要处理,直接返回
		if(!root)
		{
			return;
		}
		//没有上一个结点,自然无需比较
		//第一个结点是当前结点 < 上一个结点中的上一个结点
		//第二个结点是当前结点 < 上一个结点中的当前结点,如果有多次这样的,保留最后一个这样的当前结点
		//因此,需要记录上一个结点
		//对根节点进行处理,如果上一个结点为空,说明当前结点是根节点,则无需处理
		if(previousNode)
		{
			//比较当前结点和上一个结点
			if(root->val < previousNode->val)
			{
				if(!resultNode1)
				{
					resultNode1 = previousNode;
				}
				resultNode2 = root;
			}
		}
		previousNode = root;//更新上一个结点为当前结点,这里的更新好像是错误的,上一个结点并不是父节点
		inOrder(root->left , previousNode , resultNode1 , resultNode2);
		inOrder(root->right, previousNode , resultNode1 , resultNode2);
	}

    void recoverTree(TreeNode* root) {
        if(!root)
		{
			return ;
		}
		inOrder_iteration(root);
		//初始的时候,记录上一个结点为空
		/*
		TreeNode* previousNode = NULL;
		TreeNode* resultNode1 = NULL;
		TreeNode* resultNode2 = NULL;
		inOrder(root , previousNode , resultNode1 , resultNode2);

		//找到两个结点后,下面开始交换结点内数值
		if(resultNode1 && resultNode2)
		{
			int temp = resultNode1->val;
			resultNode1->val = resultNode2->val;
			resultNode2->val = temp;
		}
		*/
    }
};

const int MAXSIZE = 1000;
TreeNode gNodeArr[MAXSIZE];
int gIndex;
TreeNode* createNode(int value)
{
	++gIndex;
	gNodeArr[gIndex].val = value;
	return &gNodeArr[gIndex];
}

//构建二叉树,这里默认首个元素为二叉树根节点,然后接下来按照作为每个结点的左右孩子的顺序遍历
TreeNode* buildBinaryTree(vector<int>& nums)
{
	if(nums.empty())
	{
		return NULL;
	}
	TreeNode* root;
	int size = nums.size();
	int j = 0;
	//结点i的孩子结点是2i,2i+1
	for(int i = 0 ; i < size ; i++)
	{
		if(i)
		{
			createNode(nums.at(i));
		}
		else
		{
			root = createNode(nums.at(i));
		}
	}
	//设定孩子结点指向,
	for(int i = 1 ; i <= size ; i++)
	{
		if(2 * i <= size)
		{
			gNodeArr[i].left = &gNodeArr[2*i];
		}
		if(2*i + 1 <= size)
		{
			gNodeArr[i].right = &gNodeArr[2*i + 1];
		}
	}
	//设定完了之后,返回根节点
	return root;
}

	void inOrder(TreeNode* root, vector<int>& result)
	{
		//空指针就不需要处理,直接返回
		if(!root)
		{
			return;
		}
		//第一个结点是当前结点 < 上一个结点中的上一个结点
		//第二个结点是当前结点 < 上一个结点中的当前结点,如果有多次这样的,保留最后一个这样的当前结点
		//因此,需要记录上一个结点
		inOrder(root->left , result);
		if(root)
		{
			result.push_back(root->val);
		}
		inOrder(root->right , result);
	}

void print(vector<int>& result)
{
	if(result.empty())
	{
		cout << "no result" << endl;
		return;
	}
	int size = result.size();
	for(int i = 0 ; i < size ; i++)
	{
		cout << result.at(i) << " " ;
	}
	cout << endl;
}

void process()
{
	 vector<int> nums;
	 int value;
	 int num;
	 Solution solution;
	 vector<int> result;
	 while(cin >> num )
	 {
		 nums.clear();
		 gIndex = 0;//设定下标
		 for(int i = 0 ; i < num ; i++)
		 {
			 cin >> value;
			 nums.push_back(value);
		 }
		 TreeNode* root = buildBinaryTree(nums);
		 solution.recoverTree(root);
		 //打印二叉树中序结点值
		 inOrder(root , result);
		 print(result);
	 }
}

int main(int argc , char* argv[])
{
	process();
	getchar();
	return 0;
}


		//第一个结点满足:A[i] > A[i+1]的i,同理是A[i-1] > A[i]中的上一个结点A[i-1]
		//第二个结点是满足:A[i] < A[i-1]中的最后一组的i,在比较的过程中我们总是记录上一个遍历结点的值,然后与当前值比较

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值