给定二叉树和两个节点n1和n2,编写程序以找到他们的最近公共祖先。
在做这个之前, 有些东西需要提前问清楚, 二叉树是不是二叉搜索树, 如果是二叉搜索树, 那就好处理多了. 在保证2个节点都属于此二叉树的情况下, 由于二叉搜索树是排序过的, 位于左子树的结点都比父结点小, 而位于右子树的结点都比父结点大, node.data>node.left.data && node.data>node.right.data , 我们只需要从树的根结点开始和两个输入的结点进行比较。
如果当前结点的值比两个结点的值都大,那么最低的共同父结点一定是在当前结点的左子树中,于是下一步遍历当前结点的左子结点。
如果当前结点的值比两个结点的值都小,那么最低共同父结点一定在当前结点的右子树中,于是下一步遍历当前结点的右子结点。
这样在树中从上到下找到的第一个在两个输入结点的値之间的结点,就是最低的公共祖先。
- (void)viewDidLoad {
[super viewDidLoad];
[self findCommmonTreeNode];
}
- (void)findCommmonTreeNode {
TreeNode * tree = [TreeNode nodeWithDataArray:@[@(10),@(6),@(14),@(4),@(8),@(12),@(16),@(1),@(5),@(7),@(9)]];
int p = 7;
int q = 9;
[self __findCommonTreeNode:tree withFirstData:p secondData:q];
[self __findCommonTreeNode2:tree withFirstData:p secondData:q];
TreeNode * node = [self __findCommonTreeNode3:tree withFirstData:p secondData:q];
NSLog(@"方法3,找到了 %d",node.data);
}
/// 二叉搜索树找到公共节点, 前提是这2个节点肯定在二叉树中, 如果左右节点不存在也会有打印
- (TreeNode *)__findCommonTreeNode:(TreeNode *)tree withFirstData:(int)firstData secondData:(int)secondData {
while (tree) {
if (tree.data > firstData && tree.data > secondData) {
tree = tree.leftNode;
} else if (tree.data < firstData && tree.data < secondData) {
tree = tree.rightNode;
} else {
NSLog(@"找到了%d",tree.data);
return tree;
}
}
return nil;
}
这种搜索方式的时间复杂度是 O(树的深度), 时间效率还是挺好的.
如果面试官说不是二叉搜索树, 那就还有一个需要确认的, 二叉树的每个节点上有没有指向父节点的指针, 如果有, 那么问题可以简化很多.
我们可以不考虑这个是二叉树, 把n1, n2节点的parentNode串起来组成2个链表, 就可以把问题转化成求这2个链表的第一个公共节点了.更多方法可以看这个, https://blog.youkuaiyun.com/u014600626/article/details/107296340 , 此处放了一个hash法的, 时间复杂度是O(2个链表的长度和)
把其中一个链表放到set中, 然后遍历另一个链表, 在遍历过程中找到了set中包含的元素, 那就说明是第一个公共节点了
// 哈希表/Set,O(M+N)
- (void)__findCommonNodeHash:(Node *)firstNode secondNode:(Node *)secondNode {
if (firstNode==nil || secondNode==nil) {
return;
}
NSMutableSet * set = [NSMutableSet set];
while (firstNode) {
[set addObject:firstNode];
firstNode = firstNode.next;
}
while (secondNode) {
if ([set containsObject:secondNode]) {
NSLog(@"hash法 找到了, 是%p %d",secondNode,secondNode.data);
return;
}
secondNode = secondNode.next;
}
NSLog(@"hash法 没找到");
}
如果面试官说不行, 没有指向父节点的指针, 就是一个最普通的二叉树, 那就用更通用的解法.
方案1: 保存从根节点到n1, n2 的路径, 比如找1和7的公共节点, 根节点到1的路径是10->6->4->1 , 根节点到7的路径是10->6->8->7, 最近的公共祖先就是6,
现在问题来了, 怎么保存这个路径, 可以参考 https://blog.youkuaiyun.com/u014600626/article/details/107060519 , 大致就是采用前序遍历, 然后保存每个遍历到的节点加入到数组中, 如果这个节点 == n1那就对数组进行一次copy操作, 遍历到n2也copy一次, 经过copy之后的数组就是从根节点到n1, n2 的路径了, 在从路径中找最后一个相同的节点即可. 这个需要对树遍历一次, 时间复杂度是O(n). 需要额外的空间保存路径, 空间上也是O(n).
/// 普通二叉树找到,
- (TreeNode *)__findCommonTreeNode2:(TreeNode *)tree withFirstData:(int)firstData secondData:(int)secondData {
// 找第一个节点的路径
NSArray<TreeNode *> * firstArray = [self __findPathInTreeNode:tree withData:firstData array:[NSMutableArray array]];
// 找第二个节点的路径
NSArray<TreeNode *> * secondArray = [self __findPathInTreeNode:tree withData:secondData array:[NSMutableArray array]];
// 找2个路径中最后一个相同的node
TreeNode * result = nil;
for (int i = 0; i<firstArray.count && i<secondArray.count; i++) {
if (firstArray[i] == secondArray[i]) {
result = firstArray[i] ;
}
}
NSLog(@"找到了,是%d",result.data);
return result;
}
- (NSArray *)__findPathInTreeNode:(TreeNode *)tree withData:(int)data array:(NSMutableArray *)array {
if (tree == nil) {
return array;
}
// 保存走过的路径
static NSMutableArray * pathArray = nil;
if (pathArray == nil) {
pathArray = [NSMutableArray array];
}
[pathArray addObject:tree];
if (tree.data == data) {
[array addObjectsFromArray:pathArray];
}
// 前序遍历, 根->左->右
[self __findPathInTreeNode:tree.leftNode withData:data array:array];
[self __findPathInTreeNode:tree.rightNode withData:data array:array];
[pathArray removeObject:tree];
return array;
}
方案2: 如果说辅助空间都不让用了, 还可以这样, 如果在其中的一个节点 左侧找到了n1 || n2 , 右侧也找到了n1 || n2中的一个, 那这个节点就是最近公共祖先了,
举例来说, n1 = 1, n2 = 7, 这2个节点都在10的左侧, 10的右侧没有, 所以10不是, 继续向左侧寻找, 10的左侧是6, 6的左侧找到了1, 6的右侧找到了7, 6就是最近公共祖先了.
总结一下策略,
递归结束的标志就是, 在某个节点的左字数发现一个节点, 右子树发现一个节点, 那就是此节点了;
如果左子树发现了, 右子树没有发现, 那就继续往左子树寻找;
如果右子树发现了, 左子树没有发现, 那就继续往右子树寻找;
时间复杂度的话, 只需要一次遍历树即可 , 时间复杂度是O(N),空间复杂度是O(1).
/// 普通二叉树找到, 不使用额外空间
- (TreeNode *)__findCommonTreeNode3:(TreeNode *)tree withFirstData:(int)firstData secondData:(int)secondData {
if (tree) {
NSLog(@"寻找到 %d",tree.data);
}
if (tree == nil || tree.data == firstData || tree.data == secondData) {
return tree;
}
TreeNode * left = [self __findCommonTreeNode3:tree.leftNode withFirstData:firstData secondData:secondData];
NSLog(@"在%d左侧寻找,找到了%d",tree.data,left.data);
TreeNode * right = [self __findCommonTreeNode3:tree.rightNode withFirstData:firstData secondData:secondData];
NSLog(@"在%d右侧寻找,找到了%d",tree.data,right.data);
// 左边找到一个值, 右边找到一个值, 说明就是结果了
if (left && right) {
return tree;
}
// 左边找到了,右边没找到, 继续在左边寻找
if (left) {
return left;
}
// 右边找到了,左边没找到, 继续在右边寻找
if (right) {
return right;
}
return nil;
}