11.27自顶向下 DFS

199. 二叉树的右视图

思路:BFS层序遍历或DFS深搜先右后左

BFS层序遍历,每次可以先遍历右儿子再遍历左儿子,每个循环存储第一个节点nodei==0的值。

DFS深搜时,先递归右子树,再递归左子树,当某个深度首次到达时,对应的节点就在右视图中。

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        queue<TreeNode*> que;
        vector<int> res;
        if(root)  que.push(root);       
   
        while(!que.empty()){        
          int n = que.size();
          for (int i = 0; i < n; i++) {
             TreeNode* node = que.front();
             que.pop();
             if(i == 0) res.push_back(node->val);
             if(node->right) que.push(node->right);
             if(node->left) que.push(node->left);           
          }
        }
        return res;
    }
};
class Solution {
    vector<int> res;//直接定义全局变量数组,减少dfs里的参数
    void dfs(TreeNode* node , int depth ){
      if(!node) return;
      if(depth == res.size()) res.push_back(node->val);
		//当深度首次到达时,记录节点值
      dfs(node->right , depth + 1 );//先递归右子树,保证首次遇到的一定是最右边的节点
      dfs(node->left , depth + 1 );
    }
    
public:
    vector<int> rightSideView(TreeNode* root) {      
        dfs(root , 0 );
        return res;
    }
};

1448. 统计二叉树中好节点的数目

思路:搜索所有路径,记录路径上的最大值mx,定义全局变量res,每次节点值大于mx , res++。

有个坑是以为需要剪枝,遇到node->val < mx就返回,这是错的。样例1告诉我们需要遍历所有节点。所以本题返回条件只有遇到空节点。

image-20241127150814222

class Solution {
    int res;
    void dfs(TreeNode* node , int mx){
      if(!node) return;
     
      if(node->val >= mx){
          res ++;
          mx = node->val;
        }
      if(node->left) dfs(node->left , mx);
      if(node->right) dfs(node->right , mx);
      
    }
public:
    int goodNodes(TreeNode* root) {
        dfs(root , INT_MIN);
        return res;
    }
};

灵神版:

我们可以在向下递归的同时,额外维护一个参数 mx 表示从根节点到当前节点之前,路径上的最大节点值。

  • 如果当前节点为空,到达递归边界,返回 0。
  • 递归左子树 goodNodes(root.left, max(mx, root.val)),获取左子树的好节点个数 left。
  • 递归右子树 goodNodes(root.right, max(mx, root.val)),获取右子树的好节点个数 right。
  • 如果当前节点是好节点,即 mx <= root.val,那么返回 left+right+1。否则返回 left+right
class Solution {
public:
    int goodNodes(TreeNode *root, int mx = INT_MIN) {
        if (root == nullptr)
            return 0;
        int left = goodNodes(root->left, max(mx, root->val));
        int right = goodNodes(root->right, max(mx, root->val));
        return left + right + (root->val >= mx);
    }
};


1457. 二叉树中的伪回文路径

思路:

  1. 题目允许把路径上的节点值重新排列。如果能排列成回文序列,按照长度的奇偶性分类讨论:
  • 偶回文序列:左半边和右半边是一一对应的。比如 11222211,左半边有两个 1,右半边肯定也有两个 1。所以每个数字的出现次数均为偶数。对于长为偶数的路径,只要路径上的每个数字的出现次数均为偶数,就一定可以重新排列成回文序列。

  • 奇回文序列:把正中间的那个数拿出来,就变成偶回文序列了。比如 112232211,拿出正中间的 3 就和上面一样。所以恰好有一个数字的出现次数为奇数,其余数字的出现次数均为偶数。对于长为奇数的路径,只要路径上数字的出现次数满足该要求,就一定可以重新排列成回文序列。

综合起来就是奇数个数的数的个数<=1

  1. 如何维护出现次数的奇偶性?

有两种写法:数组 / 位运算

用一个数组 p 维护路径上 1 到 9 每个数字出现次数的奇偶性。如果一个数出现偶数次,则记录为 0;如果出现奇数次,则记录为 1。递归之前,所有数都出现 0 次,所以数组元素都初始化成 0。

从根节点开始递归,假设我们遇到了 5,如果 p[5]=0,那么把 p[5] 从 0 改成 1,否则从 1 改成 0,这可以用「异或 1」实现,即 p[5] ^= 1。(p[5] = p[5] ^ 1)

当递归到叶子时,如果发现数组 p 中的 1 的个数是 0 个或者 1 个,那么说明我们可以得到一个回文序列,返回 1,否则返回 0。

//自己的写法:搜索所有路径,用一个哈希表统计路径中数字的个数,最后遍历个数为奇数的数如果<=1则ans++,结果好像溢出了,暂时没找到原因。
class Solution {
    vector<unordered_map<int , int>> res;
    void dfs(TreeNode* node , unordered_map<int , int>& mp){
      if(!node) return;
      mp[node->val] ++;
      if(!node->left && !node->right) res.push_back(mp);
      dfs(node->left , mp);
      dfs(node->right , mp);
    }
public:
    int pseudoPalindromicPaths (TreeNode* root) {
      int ans , odd = 0;
      unordered_map<int , int> mp;
      dfs(root , mp);
      for (auto mp : res){
        for (int i = 0 ; i < mp.size() ; i++){
          if(mp[i] % 2 == 1) odd += 1;
        }
        if(odd <= 1) ans ++;
      }
      return ans;
    }
};

展开:

C++中unorder_map的三种插入方式:emplace赋值insert,并通过示例展示了它们的区别。其中,emplace在遇到相同key时拒绝插入赋值会更新已有key的value而insert的行为与emplace相同。在需要更新value时,应使用赋值方式而非emplace。

灵神的题解

用数组维护:

class Solution {
    int dfs(TreeNode *node, array<int, 10> &p) {
        if (node == nullptr) {
            return 0;
        }
        p[node->val] ^= 1; // 修改 node->val 出现次数的奇偶性
        int res;
        if (node->left == node->right) { // node 是叶子节点
            res = accumulate(p.begin(), p.end(), 0) <= 1;
        } else {
            res = dfs(node->left, p) + dfs(node->right, p);
        }
        // 恢复到递归 node 之前的状态(不做这一步就把 node->val 算到其它路径中了),体现回溯的思路。
        p[node->val] ^= 1;
        return res;
    }

public:
    int pseudoPalindromicPaths(TreeNode *root) {
        array<int, 10> p{};
        return dfs(root, p);
    }
};

注释:accumulatenumeric库中的一个函数,主要用来对指定范围内元素求和,但也自行指定一些其他操作,如范围内所有元素相乘、相除等。

使用前需要引入相应的头文件。

#include <numeric>

函数共有四个参数,其中前三个为必须,第四个为非必需。

若不指定第四个参数,则默认对范围内的元素进行累加操作

accumulate(起始迭代器, 结束迭代器, 初始值, 自定义操作函数)

accumulate函数将它的一个内部变量设置为指定的初始值,然后在此初值上累加输入范围内所有元素的值。accumulate算法返回累加的结果,其返回类型就是其第三个实参的类型。

可以使用accumulate把string型的vector容器中的元素连接起来:

string sum = accumulate(v.begin() , v.end() , string(" "));  

这个函数调用的效果是:从空字符串开始,把vec里的每个元素连接成一个字符串。

它擅长于将一个范围汇总为单一值,但不擅长于对每个元素进行函数应用并收集结果。使用时要时刻谨记这一原则,以确保代码清晰易懂,体现算法的本意。

  res = accumulate(p.begin(), p.end(), 0) <= 1;

额外对这行解释,因为p数组元素值只有0/1,因此对其和进行累加的值,就是奇数的个数,因此与1比较,如果大于1,res = 0 ; <=1 , res = 1。

写法二:用位运算维护

由于 p 中只有 0 和 1,所以可以把 p 压缩成一个二进制数 mask,它的每个比特记录着 0 和 1。

image-20241127211329452

如果 mask 中只有一个 1,那么去掉这个 1 之后,mask 就变成 0 了,所以可以用上面这篇文章中的「删除最小元素」的方法,判断mask&(mask−1)=0是否成立,如果成立则说明 mask 中要么只有一个 1,要么全为 0。

class Solution {
public:
    int pseudoPalindromicPaths(TreeNode *root, int mask = 0) {
        if (root == nullptr) {
            return 0;
        }
        mask ^= 1 << root->val; // 修改 root->val 出现次数的奇偶性
        if (root->left == root->right) { // root 是叶子节点
            return (mask & (mask - 1)) == 0;
        }
        return pseudoPalindromicPaths(root->left, mask) +
               pseudoPalindromicPaths(root->right, mask);
    }
};

复杂度分析

  • 时间复杂度:O(n),其中 n 为二叉树的节点个数。
  • 空间复杂度:O(n)。最坏情况下,二叉树退化成一条链,递归需要 O(n) 的栈空间。

位运算

分享|从集合论到位运算,常见位运算技巧分类总结! - 力扣(LeetCode)


1315. 祖父节点值为偶数的节点和

思路:深搜递归,遇到偶数节点就判断孙子节点是否存在,是就加上值。

class Solution {
    int res;
    void dfs(TreeNode* node){
      if(!node) return;
      if(node->val % 2 == 0){
        if(node->left){
          if(node->left->left) res += node->left->left->val;
          if(node->left->right) res += node->left->right->val;
        } 
        if(node->right){
          if(node->right->left) res += node->right->left->val; 
          if(node->right->right) res += node->right->right->val; 
        }
      }
      dfs(node->left);
      dfs(node->right);
    }
public:
    int sumEvenGrandparent(TreeNode* root) {
        dfs(root);
        return res;
    }
};
posted @ 2024-11-27 21:39  七龙猪  阅读(1)  评论(0)    收藏  举报
-->