七、二叉树学习4(代码随想录学习)

21. 合并二叉树

leetcode链接

思路: 使用递归+先序遍历 合并二叉树

确定递归函数的参数和返回值:首先要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。

确定终止条件:因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了(如果t2也为NULL也无所谓,合并之后就是NULL)。反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。

确定单层递归的逻辑:重复利用一下t1这个树,t1就是合并之后树的根节点

class Solution {
public:
	// 返回root1与root2合并后的树
	TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
		// 跳出循环条件,其中一个为空,合并结果为另一个
		if (root1 == NULL) return root2;
		if (root2 == NULL) return root1;
		root1->val += root2->val;
		// 递归合并左/右子树
		root1->left = mergeTrees(root1->left, root2->left);
		root1->right = mergeTrees(root1->right, root2->right);

		return root1;
	}
};

22. 二叉搜索树中的搜索

leetcode链接

思路: 利用二叉树搜索树的特性,判断当前根节点的值与目标值的关系,然后进一步向下进行搜索或返回当前指针。

递归法:

class Solution {
public:
	TreeNode* searchBST(TreeNode* root, int val) {
		if (root == NULL) return NULL;
		if (root->val == val) return root;

		TreeNode* res = NULL;
		if (root->val > val) res = searchBST(root->left, val);
		if (root->val < val) res = searchBST(root->right, val);
		return res;
	}
};

迭代法:

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        while (root != NULL) {
            if (root->val > val) root = root->left;
            else if (root->val < val) root = root->right;
            else return root;
        }
        return NULL;
    }
};

23. 验证二叉搜索树

leetcode链接

思路: 要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。

递归法1: 使用一个数组保存二叉树中序遍历的结果,判断数组是否时递增(不包含重复元素)

class Solution {
public:
	void inorder(vector<int> &nums,TreeNode* root) {
		if (root == NULL) return;
		inorder(nums, root->left);
		nums.push_back(root->val);
		inorder(nums, root->right);
		return;
	}

	bool isValidBST(TreeNode* root) {
		vector <int> nums;
		inorder(nums,root);
		if (nums.size() == 0 || nums.size() == 1)
			return true;
		for (int i = 0, j = 1; j < nums.size(); i++, j++)
			if (nums[i] >= nums[j]) // 当存在重复元素或左边比右边大时,返回false
				return false;
		return true;
	}
};

递归法2:使用变量保存当前中序遍历中的最大值,判断是否为当前中序遍历的最后一个结点,直至递归完整颗树。

class Solution {
public:
	TreeNode* cur = NULL; // 记录中序遍历中,当前结点的上一个结点
	// 输入一个二叉树,判断是否为二叉搜索树
	bool isValidBST(TreeNode* root) {
		if (root == NULL) return true;
		// 中序遍历,判断每个子树是否符合要求
		bool left = isValidBST(root->left);
		if (cur != NULL && cur->val >= root->val)
			return false;
		cur = root; // 记录当前结点
		bool right = isValidBST(root->right);
		return left && right;
	}
};

迭代法:使用迭代法来进行中序遍历,其他与递归法2的思路一致。

class Solution {
public:
	// 输入一个二叉树,判断是否为二叉搜索树
	bool isValidBST(TreeNode* root) {
		if (root == NULL) return true;
		// 中序遍历,判断每个子树是否符合要求
		stack<TreeNode*> st;
		TreeNode* cur = root;
		TreeNode* pre = NULL;
		while (!st.empty() || cur != NULL) {
			if (cur != NULL) {
				st.push(cur);  // 将指针入栈
				cur = cur->left; // 向左遍历
			}
			else {
				cur = st.top();
				st.pop();
				if (pre != NULL && pre->val >= cur->val)
					return false;
				pre = cur;
				cur = cur->right;
			}
		}
		return true;
	}
};

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

leetcode链接

思路: 将二叉搜索树中序遍历,即可得到有序数组,对有序数组求最小差值。

写法1:使用数组保存中序遍历的结果

class Solution {
public:
	vector<int> res;
	void inorder(TreeNode* root) {
		if (root == NULL) return;
		inorder(root->left);
		res.push_back(root->val);
		inorder(root->right);
	}

	int getMinimumDifference(TreeNode* root) {
		inorder(root);  // 中序遍历得到有序数组
		int minGap = res[1] - res[0];
		// 统计有序数组的最小差值
		for (int i = 1; i < res.size(); i++) {
			if (res[i] - res[i - 1] < minGap)
				minGap = res[i] - res[i - 1];
		}
		return minGap;
	}
};

写法2:不使用额外的数组,需要用一个pre节点记录一下cur节点的前一个节点,在递归中返回判断是否为最小值。

class Solution {
public:
	int res = INT_MAX;
	TreeNode* pre = NULL;
	void inorder(TreeNode* root) {
		if (root == NULL) return;
		inorder(root->left);
		if (pre != NULL && root->val - pre->val < res)
			res = root ->val - pre->val;
		pre = root;
		inorder(root->right);
	}

	int getMinimumDifference(TreeNode* root) {
		inorder(root);  // 中序遍历得到有序数组
		return res;
	}
};

25. 二叉搜索树中的众数

leetcode链接

思路1: 递归遍历二叉树,使用map保存出现的元素及其出现的次数,将map转换为pair,对其出现的次数进行排序,得到出现众数。(对任意二叉树的均可求出,不限定二叉搜索树

class Solution {
public:
	unordered_map<int, int> cnt;

	bool static cmp(const pair<int, int>&a, const pair<int, int> &b) {
		return a.second > b.second; // 递减排序
	}

	void preorder(TreeNode* cur) {
		if (cur == NULL) return;
		cnt[cur->val]++;
		preorder(cur->left);
		preorder(cur->right);
	}

	vector<int> findMode(TreeNode* root) {
		preorder(root);
		// 将map转换为pair,才能对val进行排序
		vector<pair<int, int>> cntPair(cnt.begin(),cnt.end());
		sort(cntPair.begin(), cntPair.end(), cmp); // 自定义排序
		
		vector<int> res; // 保存结果
		res.push_back(cntPair[0].first);
		for (int i = 1; i < cntPair.size(); i++) {
			if (cntPair[i].second != cntPair[0].second)
				break;
			res.push_back(cntPair[i].first);
		}
		return res;
			
	}
};

时间复杂度: O(nlogn)
空间复杂度: O(n)


思路2: 递归中序遍历二叉树,在遍历的统计记录元素及当前出现最大元素的次数。若当前次数与最大次数相同,则将该元素添加进结果数组;若当前次数大于最大次数,则清空结果数组,将该元素添加。

class Solution {
private:
	int maxcount = 0;
	int count = 0;
	vector<int> res;
	TreeNode* pre = NULL;
public:
	void inorder(TreeNode* cur) {
		if (cur == NULL) return;
		inorder(cur->left);
		if (pre == NULL) count = 1;//当pre==null,表示第一个元素,count = 1
		else if (pre->val == cur->val) count++; // 当前一个元素与当前元素值相等,count+1
		else count = 1; // 当二者值不相等时,count = 1
		pre = cur;  // 修改pre指针
		if (count == maxcount)  // 当 当前值出现的次数与最大次数相等时,将其添加进结果
			res.push_back(cur->val);
		if (count > maxcount) {  // 当 当前值出现次数大于最大次数时,清空结果数组,输入当前值
			maxcount = count;  // 更新最大次数
			res.clear();
			res.push_back(cur->val);
		}
		inorder(cur->right);
		return;
	}

	vector<int> findMode(TreeNode* root) {
		inorder(root);
		return res;
	}
};

时间复杂度: O(n)
空间复杂度: O(n)

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

leetcode链接

思路: 利用后序遍历,从下往上判断,左右子树是否包含p/q。若当前的左右结点均包含p/q,则当前结点为最近公共祖先,返回该节点;若当前结点只有一边包含p/q,则返回包含p/q的一边的结点,继续向上遍历;若均不包含p/q,则返回null。

情况一: 当p/q中,二者均不为最近公共祖先时,该思路会进行后序遍历,找到符合条件的最近公共祖先。
情况二: 当p/q中,某结点为最近公共祖先时。假使p为最近公共祖先,q为p的后代。当遍历到p时,会直接返回,而不会遍历到q。遍历完整棵树,会返回p,符合题意。

class Solution {
public:
	// 当前根节点中,p与q的最近公共祖先
	TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		if (root == NULL) return root;
		// 当为p/q时,返回
		if (root == p || root == q) return root;
		// 后序遍历
		TreeNode*  left = lowestCommonAncestor(root->left, p, q);
		TreeNode* right = lowestCommonAncestor(root->right, p, q);
		if (left != NULL && right != NULL)  // 当左右子树均存在p/q时,则当前结点时最小公共子树
			return root;
		else if (left == NULL && right != NULL) // 当一边存在时,返回存在的一方
			return right;
		else if (left != NULL && right == NULL)
			return left;
		else  // 均不存在p/q,返回null
			return NULL;
	}
};

总结:

  1. 求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。
  2. 在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。
  3. 要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果。

27. 二叉树周末总结

代码随想录:二叉树总结4

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