Day18 二叉树:530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236.二叉树的最近公共祖先

118 阅读8分钟

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

题目链接:530.二叉搜索树的最小绝对差

难度指数:😀🙂

递归

直白想法:(暴力)

先进行中序遍历,把二叉搜索树转变成一个有序的数组,然后就找相邻的最小绝对差。

双指针优化:

在中序遍历时,利用2个指针直接得出最小绝对差是多少,(而无需把二叉搜索树转变成数组,这样多开辟了空间,太折腾。)

cur指向的数值减去pre指向的数值,这个差值就是相邻两个结点的差值,再用一个result记录这些差值中的最小值。

代码思路:

本题递归函数不需要有返回值 (建议重看视频)

 int result = MAX_INT;  //记录最小差值
 TreeNode* pre = NULL;
 void traversal(TreeNode* cur) {  //cur表示当前遍历的结点,(这里参数没有写成root,有利于你理解cur和pre的先后顺序)
     if (cur == NULL) {
         return;
     }
     traversal(cur->left);  //左
     //中的处理逻辑:比较这两个节点的差值,让result记录最小的差值
     if (pre != NULL) {
         result = min(result, cur->val - pre->val);  //min(之前result的最小值, cur的数值 - pre的数值)
     }
     pre = cur;
     
     traversal(cur->right);  //右
     
 }

if (pre != NULL) { result = min(result, cur->val - pre->val); //min(之前result的最小值, cur的数值 - pre的数值) }

保证我们一直都是取最小的。

最后的 result 记录的就是遍历整个二叉搜索树之后的最小差值

Q:关键:pre 如何紧跟着 cur ,指向 cur 的上一个节点?

A:很简单,让 pre = cur;

遍历第一个节点的时候,是不需要进行两个节点的相减,

建议10:00重新看看

AC代码: (核心代码模式)

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
  *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  * };
  */
 class Solution {
 private:
     int result = INT_MAX;
     TreeNode* pre = NULL;
     void traversal(TreeNode* cur) {
         if (cur == NULL) {
             return;
         }
         traversal(cur->left);  //左
         //中
         if (pre != NULL) {
             result = min(result, cur->val - pre->val);
         }
         pre = cur;
 ​
         traversal(cur->right);  //右
     }
 public:
     int getMinimumDifference(TreeNode* root) {
         traversal(root);
         return result;
     }
 };

迭代

代码思路:

 ​

501.二叉搜索树中的众数

题目链接:501.二叉搜索树中的众数

难度指数:😀😕

本题对二叉搜索树进行了重新定义,在这颗二叉搜索树中允许有重复的元素。

最终要求输出众数的集合,把所有的众数放到一个数组里。

递归

直白想法:(暴力)

当成是普通二叉树来处理

AC代码: (核心代码模式)

 ​

双指针优化:

利用二叉搜索树的特性:其中序遍历是有序的

代码思路:

Q:如何在有序的二叉树中求众数?

A:给你一个有序的数组,让你求众数,so easy;但给你一棵有序的二叉树,让你求众数,懵了吧。难在遍历方式上面

求众数的具体方法:

Count 是统计单个元素出现的频率maxCount 是所有这些元素中最高频率

如果 curpre 指向的值相等,就统计 Count ,(当 cur 指向遍历的第一个元素,此时还没有 pre ,那么默认的 Count 就是1 )

Q:返回值为什么是 void

A:因为我们遍历的是整棵二叉树,而且不需要递归函数给我们返回什么,因为我们在遍历的过程中直接对元素进行统计,结果也全都放在全局变量里,所以不需要有返回值。

是处理逻辑,需要统计这个元素出现的频率,即Count

 TreeNode* pre = NULL;
 int Count = 0;  //统计单个元素出现的频率
 int maxCount = 0;  //统计整个二叉树中元素出现的最高频率   (至少是统计出现过的)
 vector<int> result;  //放最终结果
 //开始遍历
 void traversal(TreeNode* cur) {
     if (cur == NULL) {
         return;
     }
     traversal(cur->left);  //左
     //中
     if(pre == NULL) {
         Count = 1;
     }
     else if (pre->val == cur->val) {  //pre和cur指向的数值相等
         Count++;
     }
     else {  //pre和cur指向的数值不相等
         Count = 1;  //就又回到1
     }
     //让pre跟在cur后面
     pre = cur;
     
     if (Count == maxCount) {
         result.push_back(cur->val);
     }
     if (Count > maxCount) {  //有找到比当前maxCount更高频率的
         maxCount = Count;  //就该更新maxCount了
         result.clear();  //清空result数组中的旧元素
         result.push_back(cur->val);
     }
     
     //右
     traversal(cur->right);
     
     return;
 }

if (Count == maxCount) { result.push_back(cur->val); }

写到这一步,有人可能会开始疑惑:maxCount好像还没有赋值啊,还是0,你直接就要if (Count == maxCount),就把这个当前数值放到result里面,那么这个结果集result数组里面是不是放的不是我们想要的结果。

别担心,后面还会有逻辑来更新这个result数组。

if (Count > maxCount) { //有找到比当前maxCount更高频率的 maxCount = Count; //就该更新maxCount了 }

接下来需要清空result数组,

因为maxCount更新了,说明前面那个 if语句 以旧的maxCount值的判断逻辑,把对应的数值放到result数组里,result数组里的元素不是我们真正的结果集想要的元素。(即旧的那些已经不是二叉搜索树中频率最高的元素,因为maxCount已经被更新了)

所以需要将之前result数组中放的结果都清空。

AC代码: (核心代码模式)

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
  *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
  *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
  * };
  */
 class Solution {
 private:
     TreeNode* pre = NULL;
     int Count = 0;  //统计单个元素出现的频率
     int maxCount = 0;  //统计整个二叉树中元素出现的最高频率  (至少是统计出现过的)
     vector<int> result;  //放最终结果
     void traversal(TreeNode* cur) {
         if (cur == NULL) {
             return;
         }
         traversal(cur->left);  //左
 ​
         //中
         if (pre == NULL) {
             Count = 1;
         }
         else if (pre->val == cur->val) {  //pre和cur指向的数值相同
             Count++;
         }
         else {  //pre和cur指向的数值不相同
             Count = 1;  //开始统计cur指向的新的元素出现的频率
         }
         //让pre跟在cur的后面
         pre = cur;
 ​
         if (Count == maxCount) {
             result.push_back(cur->val);
         }
         if (Count > maxCount) {  //有找到比当前maxCount更高频率的
             maxCount = Count;  //更新maxCount
             result.clear();  //记得清空result数组中的旧元素
             result.push_back(cur->val);
         }
 ​
         traversal(cur->right);  //右
 ​
         return;
     }
 public:
     vector<int> findMode(TreeNode* root) {
         TreeNode* pre = NULL;
         int Count = 0;
         int maxCount = 0;
         result.clear();
 ​
         traversal(root);
         return result;
     }
 };

总结:

……

你要说有涉及到什么算法吗?其实也没有。

这些就是写代码的一些悟性,或者说是代码功底,并没有涉及到什么算法,

但是这些代码的技巧确实能帮你减小代码的运行时间,

所以代码能力或者说工程能力,有时候不是非得学什么高深的算法,有时候就是考虑一些细节问题,就能让代码的运行效率提高。

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

题目链接:236.二叉树的最近公共祖先

难度指数:😀😐😕

找到p和q,从底往上遍历,一旦交汇,那这个节点就是最近公共祖先。想法很直观,但是二叉树只能从根节点开始往下遍历。

其实,遍历二叉树是没法从下往上遍历,但是,处理顺序是可以从下往上处理的。

在二叉树中,有递归就会有回溯。回溯的过程,就会从底往上处理结果。

通过回溯就可以判断某个节点的左子树有没有出现过p,右子树有没有出现过q。

若有,将这个7向上返回。最后,p和q的公共祖先的值就传到根节点。

想在回溯中达到这种效果,一定要用后序遍历。

18.01.png

18.02.png

18.03.png

05:30

……

我们只需判断:左子树中只要出现了p或q,就往上返回;右子树只要出现p或q,就往上返回。

左不为空,右也不为空,那么这个就一定是p和q的最近公共祖先。(这是第一种情况)

第二种情况:如果p是6,q是7,那么q的值本来就是p和q的公共祖先。

(在处理第一种情况的时候,就可以把第二种情况顺便处理了,即处理逻辑是可以重合的。)

同学疑惑:“这个判断条件可能有漏洞啊。” ?

若底下的这两个红色节点都是6,这个判断逻辑也会判断这个7是公共祖先呀。

其实题目信息说得很清楚:二叉树每个数值都是不同的,并且二叉树中一定存在p和q。

代码思路:(后序)

递归函数的返回值是返回二叉树中p和q的公共祖先。

 TreeNode* traversal(root, q, p) {
     if (root == NULL) {  //若传进来是空
         return NULL;  //则return也是空
     }
     //另一个终止条件
     if (root == p || root == q) {  //root在往下遍历的过程中,遇到p或q
         return root;  //就是将p或q返回
     }
     
     //单层递归的逻辑
     TreeNode* left = traversal(root->left, q, p);  //左
     TreeNode* right = traversal(root->right, q, p);  //右
     //中
     if (left != NULL && right != NULL) {
         return root;
     }
     if (left == NULL && right != NULL) {
         return right;
     }
     else if (left != NULL && right == NULL) {
         return left;
     }
     else {
         return NULL;
     }
 }

题目思路还是有点难的,要注意一些细节。

root 在往下遍历的过程中,如果遇见了 p要把 p 往上返回,告诉上一层节点:这里遇到了 p

left 告诉我们:左子树中有没有出现过 pq

这道题理解得并不深刻,二刷的时候重点关注

AC代码: (核心代码模式)

 /**
  * Definition for a binary tree node.
  * struct TreeNode {
  *     int val;
  *     TreeNode *left;
  *     TreeNode *right;
  *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
  * };
  */
 class Solution {
 public:
     TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
         if (root == NULL) {
             return NULL;
         }
         //另一个终止条件
         if (root == p || root == q) {  //root在往下遍历的过程中,遇到p或q
         return root;
         }
         //单层递归的逻辑
         TreeNode* left = lowestCommonAncestor(root->left, p, q);  //左
         TreeNode* right = lowestCommonAncestor(root->right, p, q);  //右
         //中
         if(left != NULL && right != NULL) {
             return root;
         }
         if (left == NULL && right != NULL) {
             return right;
         }
         else if (left != NULL && right == NULL) {
             return left;
         }
         else {
             return NULL;
         }
     }
 };