本周主要完成的是与DFS相关的题目,一道是Recover Binary Search Tree,另一道是Binary Tree Maximum Path Sum。
一、Recover Binary Search Tree
题目描述:
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?
分析:
1、什么是Binary Search Tree?
答:二叉搜索树是指通过中序遍历处理一个二叉树时,其关键字的输出是一个有序数组。即如果A是B的左子树的一个结点,那么A.key
≤
B.key;如果A是B的右子树的一个结点,那么A.key
≥
B.key。
比如如下所示的二叉树,其中序遍历的输出为:1234567,是一个有序数组。
2、怎么解决空间是 O(1) 的要求?
答:对于上述问题,如果使用传统的中序遍历过程 ,那么递归使用函数得到序列再进行重新构建的话,需要
O(n)
的空间,考虑到题目要求使用常数空间,即
O(1)
,那么可以依然使用中序遍历,同时使用三个指针:pre指针记录前一个遍历的结点的值,big指针记录交换后较大的值的结点位置,small指针记录交换后较小的值的结点位置。
因为题目中说到了是只有两个元素进行交换,那么假设交换后的BST如下图所示:
此时的中序遍历输出为7234561,可以看到在遍历比较7和2的时候,7大于2,7是被交换后的较大的值的结点;在遍历到6和1的时候,6大于1,1是被交换后较小的值的结点,也就是说,在中序遍历中会有两次前一结点的关键值大于当前结点关键值的情形:
- 第一次情形中前一个较大的结点就是交换的两个元素中较大的元素;
- 第二次情形中当前关键值较小的结点就是交换的两个元素中较小的元素。
利用big和small指针记录这两个结点位置,最后交换两个结点的值即可。每一个结点都可以看做一棵树,只是说它们不一定都有无子树,比如结点7没有子树,而结点2有左右子树,即在对每一个结点进行完左子树遍历后可直接将pre指向根节点位置(如果没有子树那么根节点就是这个结点本身。
通过上述分析可以得到整个代码过程如下:
class Solution {
public:
TreeNode *prev = NULL, *small = NULL, *big = NULL;
void recoverTree(TreeNode* root) {
if(root == NULL) return;
traversal(root);// 对数进行中序遍历
swap(big->val,small->val);
}
void traversal(TreeNode* root){
// 空结点,不做遍历
if(root == NULL) return;
// 遍历左子树
traversal(root->left);
// 前一结点的值应该比现结点的值小,如果违反,现结点的值被交换了
// 记录现有结点的值
if(prev && prev->val > root->val){
big = (big == NULL)?prev:big; // 记录发现的第一个大的值
small = root;//记录后面发现的被交换的小的值
}
prev = root; // 遍历根结点
traversal(root->right); // 遍历右子树
}
};
二、Binary Tree Maximum Path Sum
题目描述:
Given a binary tree, find the maximum path sum.
For this problem, a path is defined as any sequence of nodes from some starting node to any node in the tree along the parent-child connections. The path must contain at least one node and does not need to go through the root.
For example:
1 / \ 2 3
Return 6.
分析:
其实这个题比较简单,分为几种情况处理:
1、如果一个点是根结点,那么经过这个结点的最长路径和为:
- 左子树路径和+根结点关键值;
- 右子树路径和+根结点关键值;
- 左子树路径和+右子树路径和+根结点关键值;
- 根结点关键值(左子树路径和和右子树路径和都小于等于0)。
2、如果一个点是子树结点,那么就看这个点的关键值与哪一边子树的路径和更大,返回更大的值。
整体而言,通过递归即可实现对于子树的最大路径和的寻找,之后判断是否会大于当前最大路径和来选择路径更新。算法的复杂度为
O(n)
.
通过上述分析可以得到整个代码过程如下:
class Solution {
public:
int maxsum = INT_MIN;
int maxPathSum(TreeNode* root) {
if(root == NULL) return 0;
findMaxPathSum(root);
return maxsum;
}
int findMaxPathSum(TreeNode* root){
if(root == NULL) return 0;
// 找到左子树的最大路径和,如果小于0就为0
int leftmax = max(0,findMaxPathSum(root->left));
// 找到右子树的最大路径和,如果小于0就为0
int rightmax = max(0,findMaxPathSum(root->right));
// 看最长路径和是否经过根结点
maxsum = max(rightmax + leftmax + root->val,maxsum);
// 求子树的最长路径和时,只能选择一边子树
return root->val+max(leftmax,rightmax);
}
};