BinaryTree_1_分解问题

  1. 226.翻转二叉树
  2. 116.填充每个节点的下一个右侧节点指针
  3. 114.二叉树展开为链表
  4. 652.寻找重复的子树
  5. 101.对称二叉树
  6. 236.二叉树的最近公共祖先

226.翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:输入:root = [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1]

示例 2:输入:root = [2,1,3] 输出:[2,3,1]

示例 3:输入:root = [] 输出:[]

分析:
1.尝试用两种思维解决这个问题
2.遍历思维, 从上往下翻转

class Solution {
 public:
  TreeNode* invertTree(TreeNode* root) {
    if (root == nullptr) {
      return nullptr;
    }
    TreeNode* intermediary = root->left;
    root->left = root->right;
    root->right = intermediary;
    invertTree(root->left);
    invertTree(root->right);
    return root;
  }
};

3.采用分解问题的思维, 原问题翻转之后的效果等于左子树翻转完, 右子树翻转完之后再左右翻转

class Solution {
 public:
  TreeNode* invertTree(TreeNode* root) {
    if (root == nullptr) {
      return root;
    }
    TreeNode* lInverted = invertTree(root->left);
    TreeNode* rInverted = invertTree(root->right);
    root->left = rInverted;
    root->right = lInverted;
    return root;
  }
};

116.填充每个节点的下一个右侧节点指针

给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

class Node {
 public:
  int val;
  Node* left;
  Node* right;
  Node* next;
  Node() : val(0), left(NULL), right(NULL), next(NULL) {}
  Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
  Node(int _val, Node* _left, Node* _right, Node* _next)
      : val(_val), left(_left), right(_right), next(_next) {}
};

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。初始状态下,所有 next 指针都被设置为 NULL。

分析:
1.这道题用层次遍历可解, 看能否用直接遍历求解
2.同一个父节点的两个叶子节点是能关联的, 不属于同一个父节点的叶子节点怎么关联?
3.假如我们只需要把同一个父节点的两个节点关联起来, 只需要递归地进行串联

void dfs(Node* leftNode, Node* rightNode) {
  if (leftNode == nullptr || rightNode == nullptr) {
    return;
  }
  leftNode->next = rightNode;
  dfs(leftNode->left, leftNode->right);
  dfs(rightNode->left, rightNode->right);
  return;
}

4.上述做法可以把任何一个节点的左右节点关联起来, 但不能将非同一个根节点关联起来, 怎么将非同一个根节点关联起来呢?
只需要在遍历的时候多一个操作traverse(leftNode->right, rightNode->left)

void dfs(Node* leftNode, Node* rightNode) {
  if (leftNode == nullptr || rightNode == nullptr) {
    return;
  }
  leftNode->next = rightNode;
  dfs(leftNode->left, leftNode->right);
  dfs(rightNode->left, rightNode->right);
  dfs(leftNode->right, rightNode->left);
  return;
}

完整答案

class Solution {
 public:
  void dfs(Node* leftNode, Node* rightNode) {
    if (leftNode == nullptr || rightNode == nullptr) {
      return;
    }
    leftNode->next = rightNode;
    dfs(leftNode->left, leftNode->right);
    dfs(rightNode->left, rightNode->right);
    dfs(leftNode->right, rightNode->left);
    return; 
  }
  Node* connect(Node* root) {
    if (root == nullptr) {
      return root;
    }
    dfs(root->left, root->right);
    return root;
  }
};

114.二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。

示例 1:输入:root = [1,2,5,3,4,null,6] 输出:[1,null,2,null,3,null,4,null,5,null,6]

示例 2:输入:root = [] 输出:[]

示例 3:输入:root = [0] 输出:[0]

分析:
1.先理解一下二叉树转换为链表是什么意思 ? 链表本质上就是只有一个子树的二叉树, 二叉树就是链表的第一个节点多开出来一条链表的链表
2.可以用递归/非递归先序遍历, 遍历过程中把元素存储到数组里面, 然后再遍历这个数组构建一个二叉树; 能否使用分解的方式去实现?

class Solution {
 private:
  vector<TreeNode*> list;
 public:
  // 先序转数组
  void preorderTraverse(TreeNode* root) {
    if (root == nullptr) {
      return;
    }
    list.push_back(root);
    preorderTraverse(root->left);
    preorderTraverse(root->right);
    return;
  }
  void flatten(TreeNode* root) {
    preorderTraverse(root);
    int len = list.size();
    // 迭代构建树: 左孩子置空指针, 右孩子链接起来
    for (int i = 1; i < len; ++i) {
      TreeNode* pre = list[i-1];
      TreeNode* cur = list[i];
      pre->left = nullptr;
      pre->right = cur;
    }
    return;
  }
};

2.思考能否用分解的思路递归实现, 那么就要看看怎么去实现 flatten 这种操作, 也就是找 flatten 子树和当前根节点的关系

3.如下所示 一个flatten操作可以分解为 3 步, 先把左右子树分别做 flatten, 然后将右子树接在左子树的最下面, 然后把左子树换到右子树

     1          1         1
    / \        / \         \
   2   5      2   5         2
  / \   \      \   \         \
 3   4   6      3   6         3
                 \             \
                  4             4
                                 \
                                  5
                                   \
                                    6

将三步都写出来, 得到完整答案

class Solution {
 public:
  void flatten(TreeNode* root) {
    if (root == nullptr) {
      return;
    }
    flatten(root->left);
    flatten(root->right);

    TreeNode* left = root->left;
    TreeNode* right = root->right;
    root->left = nullptr;
    root->right = left;
    TreeNode* p = root;
    while (p ->right != nullptr) {
      p = p->right;
    }
    p->right = right;
    return;
  }
};

652.寻找重复的子树

给你一棵二叉树的根节点 root ,返回所有 重复的子树 。
对于同一类的重复子树,你只需要返回其中任意 一棵 的根结点即可。
如果两棵树具有 相同的结构 和 相同的结点值 ,则认为二者是 重复 的。

示例 1:输入:root = [1,2,3,4,null,2,4,null,null,4] 输出:[[2,4],[4]]

示例 2:输入:root = [2,1,1] 输出:[[1]]

示例 3:输入:root = [2,2,2,3,null,3,null] 输出:[[2,3],[3]]

分析:
1.遍历可以得到所有的子树, 对每个子树进行序列化
2.序列化的一般操作可以写成这样的字符串 “x(左子树序列化的结果)(右子树序列化的结果)”
3.在 set 中保存重复元素时, 必须保证不是统一个 node 的同一类 node 插入的是一个相同的结构, 用 set 保存已有的结果, 这里必须用一个 map 去保存已经见到过的结果; 如果用 set 直接保存 node, 那么相同的子树会保存不同的节点

class Solution {
 private:
  unordered_set<TreeNode*> repeat;
  unordered_map<string, TreeNode*> seen;
 public:
  string dfs(TreeNode* node) {
    if (node == nullptr) {
      return "";
    }
    string serial = to_string(node->val) + "(" + dfs(node->left) + ")" + "(" + dfs(node->right) + ")";
    auto it = seen.find(serial);
    // 找到重复子树
    if (it != seen.end()) {
      repeat.insert(it->second);
      // 注意这里只能插入统一类子树的标准化形式比如第一次存放的形式, 而不是插入node
      // 不然会被判定为两个子树不一样
      // repeat.insert(node);
    } else {
      seen[serial] = node;
    }
    return serial;
  }
  vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
    dfs(root);
    vector<TreeNode*> ans;
    ans.assign(repeat.begin(), repeat.end());
    return ans;
  }
};

101.对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

示例 1:输入:root = [1,2,2,3,4,4,3] 输出:true

示例 2:输入:root = [1,2,2,null,3,null,3] 输出:false

分析:
1.采用分解的思维: 对于原问题来说, 给定一个根节点, 需要判定左子树和右子树是对称的, 怎么判定左子树和右子树是对称的
2.要想判定左子树和右子树对称性, 需要生成两个指针来同时遍历, 一个向左一个向右, 然后1生2, 2生4, 4生成8…

class Solution {
 public:
  // p 和 q 是两个同时遍历具有对称性的指针
  bool check(TreeNode* p, TreeNode* q) {
    if (p == nullptr && q == nullptr) {
      return true;
    }
    if (p == nullptr || q == nullptr) {
      return false;
    }
    return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
  }
  bool isSymmetric(TreeNode* root) {
    return check(root, root);
  }
};

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

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

       3 
     /   \
   5       1 
 /  \     /  \
6    2   0    3 
   /   \ 
  7     4

示例 2:输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出:5 解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

示例 3:输入:root = [1,2], p = 1, q = 2 输出:1

分析:
1.给定一个根节点, 然后给定树里面任意两个节点, 让我们找这两个任意节点的最低公共祖先
2.先可以判定一些非常简单的情况, 假设 p 和 q 之间本身有一个祖先关系, 也就是说 root == p || root = q 那么就说明这时候的 root 就是最近公共祖先
3.然后我们处理一些相对复杂的情况, 根节点在上面, 然后 p 和 q 在根节点的下面分布着, 下一步怎么搜索呢 ? 根节点的搜索指针可以向下移动, 可以从根节点左子树搜索 p 和 q, 也可以从根节点的右子树搜索 p 和 q; 这里有 3 种可能的情况,
(i). p 和 q 都在左子树, 那么我们就在 左子树里面递归找最低公共祖先
(ii). p 和 q 都在右子树, 那么我们就在 右子树里面递归找最低公共祖先
(iii). p 和 q 左右子树各一个, 最低公共祖先就是 root

class Solution {
 public:
  bool nodeInSubTree(TreeNode* root, TreeNode* c) {
    if (root == nullptr) {
      return false;
    }
    if (root == c) {
      return true;
    }
    return nodeInSubTree(root->left, c) || nodeInSubTree(root->left, c);
  }
  TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (root == nullptr) {
      return nullptr;
    }
    if (root == p || root == q) {
      return root;
    }
    if (nodeInSubTree(root->left, p) && nodeInSubTree(root->right, q)) {
      return lowestCommonAncestor(root->left, p, q);
    }
    if (nodeInSubTree(root->right, p) && nodeInSubTree(root->right, q)) {
      return lowestCommonAncestor(root->right, p, q);
    }
    return root
  }
};

4.我们再想一下, 既然总共就 以上 3 种情况, 这里我们也不用非要判断 p 和 q 具体要在哪个子树里面: 情况还是如上三种, 我们逐次判断 2 种情况的存在性就可以

class Solution {
 public:
  TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (root == nullptr) {
      return nullptr;
    }
    if (root == p || root == q) {
      return root;
    }
    TreeNode* lRes = lowestCommonAncestor(root->left, p, q);
    TreeNode* rRes = lowestCommonAncestor(root->right, p, q);
    if (lRes != nullptr && rRes != nullptr) {
      return root;
    } else if (lRes == nullptr) {
      return rRes;
    } else {
      return lRes;
    }
  }
};

转载请注明来源, from goldandrabbit.github.io

💰

×

Help us with donation