不懂一点算法。
读题
给定一个二叉树,找到该树中两个指定节点的最近公共祖先;输入 root 是一个数列,表示一棵树;p 和 q 是给定的两个节点,需要找到其共同祖先。
听上去和上一题找到链表相交处很像,不过值得注意的是,树中的数字可正可负可为零,不能使用负数作为标记。
Root 和 p、q 是直接通过传参方式给定的,不需要自己写函数获取,记得以前做的都是得自己 cin 或者 scanf。还挺方便。
过程
第一思路
首先需要从输入的 root 构建树?似乎是逐行从左往右存储节点的,第 n 层有 2^(n-1) 个节点,第 n 层的第 i 个节点与第 n-1 层的第 i/2 个节点相连,向上取整。
考虑使用类似链表相交的方法做,首先从 p 节点往上爬,爬到顶端的过程中标记所有节点;接着从 q 节点往上爬,判断当前节点是否是已经标记了的节点。
于是思路为:
- 划分层级:每层的节点数量是固定的,数列的长度是已知的,第 i 层有 2^(i-1) 个节点,求和有总共 n 层有 2^n-1 个节点;于是使用一个 while 循环递增临时变量 n,判断数列长度是否小于2^n-1;于是有 n 为层数。
- 判断 p 和 q 分别在哪一层- 首先找到 p、q 的位置(序号)
- 使用相同的方法找到其所处的层数 n_p 和 n_q。
 
- 将 p 向上遍历并做标记。
- 将 q 向上遍历并判断标记,记录
于是有代码:
| 12
 3
 
 | public class Solution {
 }
 
 | 
看了预设代码之后才发现已经是给定了 TreeNode 的结构,不需要自己操作数列。于是想要模仿之前官方题解的哈希写法,但是有一个问题是没有 parent 指针,只能向下不能向上。如果有 parent 指针,就基本和上一题是一样的;于是新建一颗带 parent 指针的树,按照之前官方题解的双指针写法。
有代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 
 | 
 
 
 
 
 
 
 
 class Solution {
 public:
 struct TreeNode_new {
 int val;
 TreeNode_new *left;
 TreeNode_new *right;
 TreeNode_new *parent;
 TreeNode_new(int x) : val(x), left(NULL), right(NULL), parent(NULL) {}
 };
 
 TreeNode_new* cloneWithParent(TreeNode* node, TreeNode_new* parent = nullptr) {
 if (!node) return nullptr;
 TreeNode_new* newNode = new TreeNode_new(node->val);
 newNode->parent = parent;
 newNode->left = cloneWithParent(node->left, newNode);
 newNode->right = cloneWithParent(node->right, newNode);
 return newNode;
 }
 
 TreeNode* cloneWithoutParent(TreeNode* node) {
 if (!node) return nullptr;
 TreeNode* newNode = new TreeNode(node->val);
 newNode->left = cloneWithoutParent(node->left);
 newNode->right = cloneWithoutParent(node->right);
 return newNode;
 }
 
 TreeNode* searchNode(TreeNode* root, int val) {
 if (!root)
 return nullptr;
 if (root->val == val)
 return root;
 TreeNode* left = searchNode(root->left, val);
 if (left)
 return left;
 return searchNode(root->right, val);
 }
 
 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
 
 TreeNode_new* root_new = cloneWithParent(root);
 
 TreeNode_new* p_tmp = new TreeNode_new(p->val);
 p_tmp->left = cloneWithParent(p->left);
 p_tmp->right = cloneWithParent(p->right);
 TreeNode_new* p_ori = p_tmp;
 
 TreeNode_new* q_tmp = new TreeNode_new(q->val);
 q_tmp->left = cloneWithParent(q->left);
 q_tmp->right = cloneWithParent(q->right);
 TreeNode_new* q_ori = q_tmp;
 
 while (p_tmp != q_tmp) {
 p_tmp = p_tmp ? p_tmp->parent : q_ori;
 q_tmp = q_tmp ? q_tmp->parent : p_ori;
 }
 
 if(p_tmp == nullptr){
 return root;
 }else {
 TreeNode* final_out = searchNode(root, p_tmp->val);
 return final_out;
 }
 }
 };
 
 | 
越写越复杂,结果还是错的,过不了测试;因为新的 pq 节点初始化有问题。
于是有代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 
 | 
 
 
 
 
 
 
 
 class Solution {
 public:
 struct TreeNode_new {
 int val;
 TreeNode_new *left;
 TreeNode_new *right;
 TreeNode_new *parent;
 TreeNode_new(int x) : val(x), left(NULL), right(NULL), parent(NULL) {}
 };
 
 TreeNode_new* cloneWithParent(TreeNode* node, TreeNode_new* parent = nullptr) {
 if (!node) return nullptr;
 TreeNode_new* newNode = new TreeNode_new(node->val);
 newNode->parent = parent;
 newNode->left = cloneWithParent(node->left, newNode);
 newNode->right = cloneWithParent(node->right, newNode);
 return newNode;
 }
 
 TreeNode* cloneWithoutParent(TreeNode* node) {
 if (!node) return nullptr;
 TreeNode* newNode = new TreeNode(node->val);
 newNode->left = cloneWithoutParent(node->left);
 newNode->right = cloneWithoutParent(node->right);
 return newNode;
 }
 
 TreeNode* searchNode(TreeNode* root, int val) {
 if (!root)
 return nullptr;
 if (root->val == val)
 return root;
 TreeNode* left = searchNode(root->left, val);
 if (left)
 return left;
 return searchNode(root->right, val);
 }
 
 TreeNode_new* searchNode_new(TreeNode_new* root, int val) {
 if (!root)
 return nullptr;
 if (root->val == val)
 return root;
 TreeNode_new* left = searchNode_new(root->left, val);
 if (left)
 return left;
 return searchNode_new(root->right, val);
 }
 
 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
 TreeNode_new* root_new = cloneWithParent(root);
 
 TreeNode_new* p_tmp = searchNode_new(root_new, p->val);
 TreeNode_new* p_ori = p_tmp;
 
 TreeNode_new* q_tmp = searchNode_new(root_new, q->val);
 TreeNode_new* q_ori = q_tmp;
 
 while (p_tmp != q_tmp) {
 p_tmp = p_tmp ? p_tmp->parent : q_ori;
 q_tmp = q_tmp ? q_tmp->parent : p_ori;
 }
 if(p_tmp == nullptr){
 return root;
 }else {
 TreeNode* final_out = searchNode(root, p_tmp->val);
 return final_out;
 }
 }
 };
 
 | 
结果
虽然代码很唐但还是通过了,执行用时 12 ms,吃了 19.29 MB 内存。
官方题解
方法一:递归
标记二叉树的每个节点子树是否包含 p 或 q;使用 dfs 遍历。如果一个节点是公共祖先,那么一定有:
- 其子树包含 p 和 q
- 其本身就是 p 或 q 之一,其子树包含 p 或 q 二者中的另一个。
 使用的递归方法可以说很是巧妙,执行函数时向下完成了对树的遍历搜索,return 时相当于向上回溯,执行了条件的判断。官方代码如下:| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | class Solution {public:
 TreeNode* ans;
 bool dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
 
 if (root == nullptr) return false;
 
 bool lson = dfs(root->left, p, q);
 bool rson = dfs(root->right, p, q);
 
 if ((lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson))) {
 
 ans = root;
 }
 
 return lson || rson || (root->val == p->val || root->val == q->val);
 }
 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
 dfs(root, p, q);
 return ans;
 }
 };
 
 |  
 
方法二:存储父节点使用哈希表存储父节点。
 (其实我一开始是想要这么做的,但是不是很会使用哈希表,于是暴力克隆了一整颗树。)
 官方题解代码是:| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | class Solution {public:
 unordered_map<int, TreeNode*> fa;
 unordered_map<int, bool> vis;
 void dfs(TreeNode* root){
 if (root->left != nullptr) {
 fa[root->left->val] = root;
 dfs(root->left);
 }
 if (root->right != nullptr) {
 fa[root->right->val] = root;
 dfs(root->right);
 }
 }
 TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
 fa[root->val] = nullptr;
 dfs(root);
 while (p != nullptr) {
 vis[p->val] = true;
 p = fa[p->val];
 }
 while (q != nullptr) {
 if (vis[q->val]) return q;
 q = fa[q->val];
 }
 return nullptr;
 }
 };
 
 |  
 
unordered_map<int, TreeNode*> fa中。unordered_map<int, bool> vis是记录是否已经访问过该节点。由于 val 和节点存在一一映射关系,因此可以直接用 val 作为节点的唯一标识。
收获
已严肃学习哈希表和递归,以及树的处理方式……太优雅。
初看时,发现哈希表(unordered_map)是一一映射,似乎类似 python 中的字典?——了解后才知道二者就是同样的东西,是使用哈希表实现的数据容器。Cpp 中有有序无序两种”映射”容器,map 和unordered_map,前者是所谓红黑树,对应 python 的 sortedcontainers,后者是哈希表,对应 python 的 dict。
递归可以视作是执行分阶段的正过程与逆过程;通过递归,可以实现不能直接实现的逆过程,且写法会相当简练。