力扣题目链接
如果不是二叉搜索树,那第一反应就是遍历整个树,然后用map统计频率,接着把频率排序后取最高频元素集合。
需要注意的点是,map可以对key排序但不能对value排序,因此需要把map转化为数组vector在进行排序,当然vector也需要存放类型为pair
class Solution {
public:
void dfs(TreeNode* cur, unordered_map<int, int>& map){
if(cur == NULL)
return;
map[cur -> val]++; //中。用于统计元素频率
dfs(cur->left, map);
dfs(cur->right, map);
return ;
}
bool static compare(const pair<int,int>& a, const pair<int,int>& b){
return a.second > b.second;
}
vector<int> findMode(TreeNode* root) {
unordered_map<int, int> map; //key:元素 value:元素出现频率
vector<int> result;
if (root == NULL)
return result;
dfs(root, map);
vector<pair<int, int>> vec(map.begin(),map.end()); //需要将map转为pair类型的vector方便排序
sort(vec.begin(), vec.end(), compare);
result.push_back(vec[0].first); //先将频率最高的元素放入result
for( int i = 1; i < vec.size(); i++){
if(vec[i].second == vec[0].second) //可能存在频率相同的好几个众数
result.push_back(vec[i].first);
else
break;
}
return result;
}
};
二叉搜索树的中序遍历为有序数组,因此可以通过比较相邻两个元素来获得元素的频率,比较相邻元素同样需要一个pre节点和cur节点作比较,pre初始化为NULL就可以知道什么时候是第一个元素
if (pre == NULL) { // 第一个节点
count = 1; // 频率为1
} else if (pre->val == cur->val) { // 与前一个节点数值相同
count++;
} else { // 与前一个节点数值不同
count = 1;
}
pre = cur; // 更新上一个节点
因为众数可能会有多个,所以可以第一遍遍历找出最大频率maxCount,再遍历第二次找到所有频率为maxCount的元素放进集合,这个就不具体实现了。
其实可以遍历一次就找到所有的,具体步骤如下
if (count == maxCount) { // 如果和最大值相同,放进result中
result.push_back(cur->val);
}
if (count > maxCount) { // 如果计数大于最大值
maxCount = count; // 更新最大频率
result.clear(); // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
result.push_back(cur->val);
}
class Solution {
public:
int maxCount = 0; //最大频率
int count = 0; //统计每一个遍历到的当前数的频率
TreeNode* pre = NULL; //定义pre节点用于取频率,定义为NULL方便开始
vector<int> result; //结果可能有多个所以用vector
void dfs(TreeNode* cur){
if (cur == NULL)
return;
dfs(cur -> left); //左
//接下来都是中节点的处理逻辑
if (pre == NULL){ //第一个节点
count = 1;
maxCount = 1;
}
else if (pre -> val == cur -> val){ //如果和前一个节点相同
count++;
}
else{ //如果和前一个节点不同
count = 1;
}
pre = cur; //cur就是下一个的cur的pre
if (count == maxCount) //如果和最大值相同,放进result中
result.push_back(cur -> val);
if(count > maxCount){ //频率最大值被更新
maxCount = count; //更新
result.clear(); //清空result,非常关键,因为之前result里的元素都失效了
result.push_back(cur -> val);
}
dfs(cur -> right); //右
return ;
}
vector<int> findMode(TreeNode* root) {
dfs(root);
return result;
}
};
和思路2一样,只不过是通过迭代实现中序遍历,中间节点的处理逻辑完全一致
class Solution {
public:
vector<int> findMode(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = NULL;
int maxCount = 0;
int count = 0;
vector<int> result;
while (cur != NULL || !st.empty()){
if (cur != NULL){ //指针来访问节点,访问到最底层
st.push(cur); //将访问的节点放进栈
cur = cur -> left; //左
}
else{
cur = st.top(); //中
st.pop();
if (pre == NULL) { // 第一个节点
count = 1;
} else if (pre->val == cur->val) { // 与前一个节点数值相同
count++;
} else { // 与前一个节点数值不同
count = 1;
}
if (count == maxCount) { // 如果和最大值相同,放进result中
result.push_back(cur->val);
}
if (count > maxCount) { // 如果计数大于最大值频率
maxCount = count; // 更新最大频率
result.clear(); // 很关键的一步,不要忘记清空result,之前result里的元素都失效了
result.push_back(cur->val);
}
pre = cur;
cur = cur->right; // 右
}
}
return result;
}
};
力扣题目链接
因为是要找祖先,所以自然想到希望能从下往上找,因为这样下面找到了就能直接返回到上面的节点也就是祖先。
二叉树怎样才能自底向上查找?那就是回溯,二叉树回溯的过程就是从底向上,后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值来处理中节点的逻辑。
有两种情况:
事实上这两种情况的代码实现过程是一样的,因为遇到q或者p就返回,这样也包含了q或者p本身就是公共祖先的情况。
递归三部曲:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)
if (root == q || root == p || root == NULL) return root;
if (left != NULL && right != NULL) return root;
if (left == NULL && right != NULL) return right;
else if (left != NULL && right == NULL) return left;
else { // (left == NULL && right == NULL)
return NULL;
}
本题函数有返回值,是因为回溯的过程需要递归函数的返回值做判断,但本题我们依然要遍历一整棵树。
如果递归函数有返回值,如何区分要搜索一条边,还是搜索整个树呢?
搜索一条边的写法:
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;
搜索整个树写法:
left = 递归函数(root->left); // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理; // 中
在递归函数有返回值的情况下,两者有以下区别:
如果要搜索一条边,递归函数返回值不为空的时候,立刻返回;
如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)
在本题中,想利用left和right做逻辑处理,不能立刻返回,而是要等left与right逻辑处理完之后才能返回,所以要遍历整棵树。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == p || root == q || root == NULL) //终止条件
return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q); //左
TreeNode *right = lowestCommonAncestor(root -> right, p, q); //右
//接下来确定中间节点处理
if (left && right) //找到了最近公共祖先
return root;
if (left == NULL && right != NULL)
return right;
else if (left != NULL && right == NULL)
return left;
else //左右都为NULL
return NULL;
}
};
力扣题目链接
本题和236. 二叉树的最近公共祖先的区别在于需要利用二叉搜索树的性质。
因为是有序树,所以如果中间节点是 q 和 p 的公共祖先,那么中节点的数组一定是在 [p, q]区间的。即 中节点 > p && 中节点 < q 或者 中节点 > q && 中节点 < p。
那么只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是q 和 p的公共祖先。 那问题来了,一定是最近公共祖先吗?
如下图所示,从根节点搜索,第一次遇到 cur节点是数值在[p, q]区间中,即 节点5,此时可以说明 p 和 q 一定分别存在于节点 5的左子树和右子树中。
如果从节点5继续向左遍历,那么将错过成为p的祖先, 如果从节点5继续向右遍历则错过成为q的祖先。
所以当我们从上向下去递归遍历,第一次遇到 cur节点是数值在[p, q]区间中,那么cur就是 p和q的最近公共祖先。
因此本题就很好解了,按照指定方向找到的第一个符合的节点就是最近公共祖先,不需要遍历整棵树,找到结果就可以直接返回
class Solution {
public:
TreeNode* traversal(TreeNode* cur, TreeNode* p, TreeNode* q){
if (cur == NULL) //并不必要,因为其实题目说了p、q 为不同节点且均存在于给定的二叉搜索树中
return cur;
if (cur -> val > p -> val && cur -> val > q -> val){ //cur同时大于p和q,向左
TreeNode* left = traversal(cur -> left, p, q);
if(left != NULL)
return left;
}
if (cur -> val < p -> val && cur -> val < q -> val){ //cur同时小于p和q,向右
TreeNode* right = traversal(cur -> right, p, q);
if(right != NULL)
return right;
}
//剩下cur节点在区间(p->val <= cur->val && cur->val <= q->val
//或者 (q->val <= cur->val && cur->val <= p->val)中
//此时cur就是最近公共祖先了,直接返回
return cur;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
return traversal(root, p, q);
}
};
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
while(root){
if (root->val > p->val && root->val > q->val) {
root = root->left;
}
else if (root->val < p->val && root->val < q->val) {
root = root->right;
}
else
return root;
}
return NULL; //根据题目要求上面一定会出答案,但是由于必须要返回值所以其实这里随便写个什么符合TreeNode*的都行
}
};
对于有序树,不需要回溯
直接利用自带的方向性按序寻找即可,也因此本题迭代法显得更简洁。