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

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

deque法

class Solution {
public:
	deque dqp;
	deque dqq;
	bool findNode(TreeNode* root, deque& dq, TreeNode* target) {
		dq.push_back(root);
		
		if(root == target) {
			return true;
		}
		
		bool ret = false;
		if(root->left) {
			ret = findNode(root->left, dq, target);
		}
		
		if (ret) {
			return true;
		}

		if(root->right) {
			ret = findNode(root->right, dq, target);
		}

		if (ret) {
			return true;
		}		
		
		dq.pop_back();
		return false;
	}

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		// 最近公共祖先
		findNode(root, dqp, p);
		findNode(root, dqq, q);
		
		TreeNode* ret = NULL;
		while(!dqp.empty() && !dqq.empty()) {
			if (dqp.front() == dqq.front()) {
				ret = dqp.front();
				dqp.pop_front();
				dqq.pop_front();
			} else {
				break;
			}
		}
		
		return ret;
    }
};

一次递归

class Solution {
public:
	TreeNode* ans;
	bool dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
		bool lresult = false;
		bool rresult = false;

		if (root->left) {
			lresult = dfs(root->left, p, q);
		}
		
		if (root->right) {
			rresult = dfs(root->right, p, q);
		}
		
		if (lresult && rresult) {
			// 在当前节点的左右子树上,是旁系关系
			ans = root;
		}  else if (lresult || rresult) {
			if (root == p || root == q) {
				// 在当前节点的子树上,是直系关系
				ans = root;
			}
		}
		
		return lresult || rresult || (root == p) || (root == q);
	}

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root, p, q);
        return ans;
    }
};

(lresult && rresult)
左子树和右子树均包含 p 节点或 q 节点,
如果左子树包含的是 p 节点,那么右子树只能包含 q 节点,反之亦然,
因为 p 节点和 q 节点都是不同且唯一的节点,
因此如果满足这个判断条件即可说明 root 就是要找的最近公共祖先。

(lresult || rresult) && (root == p || root == q)
考虑了 root 恰好是 p 节点或 q 节点且它的左子树或右子树有一个包含了另一个节点的情况,
因此如果满足这个判断条件亦可说明 root 就是要找的最近公共祖先。

时间复杂度:O(N),其中 N 是二叉树的节点数。
二叉树的所有节点有且只会被访问一次,因此时间复杂度为 O(N)。
空间复杂度:O(N),其中 N 是二叉树的节点数。
递归调用的栈深度取决于二叉树的高度,二叉树最坏情况下为一条链,此时高度为 N。

哈希

class Solution {
public:
	unordered_set us;
	unordered_map um;

	void dfs(TreeNode* root) {
		if (root->left) {
			um[root->left] = root;
			dfs(root->left);
		}

		if (root->right) {
			um[root->right] = root;
			dfs(root->right);
		}	
	}

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		TreeNode* ans;
        dfs(root); // root没有根结点
		
		unordered_map::iterator umit = um.find(p);
		while(umit != um.end()) {
			us.insert(umit->first);
			umit = um.find(umit->second);
		}
		
		umit = um.find(q);
		while(umit != um.end()) {
			if (us.count(umit->first))
				return umit->first;
			umit = um.find(umit->second);
		}		
		
        return root;
    }
};

class Solution {
public:
	unordered_set us;
	unordered_map um;

	void dfs(TreeNode* root) {
		if (root->left) {
			um[root->left] = root;
			dfs(root->left);
		}

		if (root->right) {
			um[root->right] = root;
			dfs(root->right);
		}	
	}

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		TreeNode* ans;
		um[root] = NULL;
        dfs(root);
		
		unordered_map::iterator umit = um.find(p);
		while(umit != um.end()) {
			us.insert(umit->first);
			umit = um.find(umit->second);
		}
		
		umit = um.find(q);
		while(umit != um.end()) {
			if (us.count(umit->first))
				return umit->first;
			umit = um.find(umit->second);
		}

        return NULL;	
    }
};

以 [3,5,1,6,2,0,8,null,null,7,4] p=5 q=3 为例
构造 unordered_map
5 3
1 3
6 5
2 5
0 1
0 8
7 2
4 2

以p来填充 unordered_set
5

以q来访问 unordered_set
访问 4
访问 2
访问 5 ok

则最近祖先是5

思路
用哈希表存储所有节点的父节点,然后就可以利用节点的父节点信息从 p 结点开始不断往上跳,
并记录已经访问过的节点,再从 q 节点开始不断往上跳,如果碰到已经访问过的节点,
那么这个节点就是要找的最近公共祖先。

算法
从根节点开始遍历整棵二叉树,用哈希表记录每个节点的父节点指针。
从 p 节点开始不断往它的祖先移动,并用数据结构记录已经访问过的祖先节点。
同样,再从 q 节点开始不断往它的祖先移动,如果有祖先已经被访问过,
即意味着这是 p 和 q 的深度最深的公共祖先,即 LCA 节点。

哈希优化

只记录需要的路径

class Solution {
public:
	unordered_set us;
	unordered_map um;
	TreeNode* gp;
    TreeNode* gq;
	
	bool dfs(TreeNode* root) {
		bool lchild = false;
		bool rchild = false;

		if (root->left) {
			lchild = dfs(root->left);
		}

		if (root->right) {
			rchild = dfs(root->right);
		}
		
		if (lchild && rchild) {
			um[root->left] = root;
			um[root->right] = root;
			return true;
		} else if (lchild) {
			um[root->left] = root;
			return true;
		} else if (rchild) {
			um[root->right] = root;
			return true;
		} else if (root == gp || root == gq) {
			return true;
		}

		return false;
	}

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		TreeNode* ans;
		gp = p;
		gq = q;
		um[root] = NULL;
        dfs(root);
		
		unordered_map::iterator umit = um.find(p);
		while(umit != um.end()) {
			us.insert(umit->first);
			umit = um.find(umit->second);
		}
		
		umit = um.find(q);
		while(umit != um.end()) {
			if (us.count(umit->first))
				return umit->first;
			umit = um.find(umit->second);
		}
		
		return NULL;
    }
};

递归优化

再想想,如果两个结点在同一棵子树,则直接返回辈分最大的结点;
如果两个结点在不同子树,则直接返回当前结点

class Solution {
public:
	TreeNode* gp;
	TreeNode* gq;
	TreeNode* ans;
	
	bool dfs(TreeNode* root) {
		bool lresult = false;
		bool rresult = false;
		if (root->left) {
			lresult = dfs(root->left);
		}
		
		if (root->right) {
			rresult = dfs(root->right);
		}
		
		if (lresult && rresult) {
			// p 和 q 分散在 root 的左右两侧
			ans = root;
			return true;
        } else if (root == gp || root == gq) {
			// 分布在一侧的时候,以辈分大的为主
			// 递归可以实现,辈分小的元素优先改,辈分大的元素延后改
			// 可以确保最后修改 ans 的是备份大的元素
			// 这个 else if 要在下一个 else if 的前面
			// 不然辈分小的返回后,就不会再修改 ans 了
			ans = root;
			return true;  
        } else if (lresult || rresult) {
			return true;
		} else {
            return false;
        }
	}
	

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		gp = p;
		gq = q;
		dfs(root);
		return ans;
    }
};

dfs一次找两个

class Solution {
public:
	deque dq1;
	deque dq2;
	TreeNode* gp;
	TreeNode* gq;

    // 找到最近祖先
    int dfs(TreeNode* root) {
        dq1.push_back(root);
        dq2.push_back(root);

        int lresult = 0;
        int rresult = 0;
        if (root->left) {
            lresult = dfs(root->left);
        }

        if (root->right) {
            rresult = dfs(root->right);
        }

        if (((lresult | rresult) == 3) ||
            (((lresult | rresult) == 2) && (root->val == gq->val)) ||
            (((lresult | rresult) == 1) && (root->val == gp->val))) {
            return 3;
        } else if (((lresult | rresult) == 2) ||
                (((lresult | rresult) == 0) && (root->val == gp->val))) {
            dq2.pop_back();
            return 2;
        } else if (((lresult | rresult) == 1) ||
                (((lresult | rresult) == 0) && (root->val == gq->val))) {
            dq1.pop_back();
            return 1;
        } else {
            dq1.pop_back();
            dq2.pop_back();
            return 0;
        }
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
		gp = p;
		gq = q;

		// 最近公共祖先
		dfs(root);
		
		TreeNode* ret = NULL;
		while(!dq1.empty() && !dq2.empty()) {
			if (dq1.front() == dq2.front()) {
				ret = dq1.front();
				dq1.pop_front();
				dq2.pop_front();
			} else {
				break;
			}
		}
		
		return ret;
    }
};

附录

us对NULL的处理

#include 
#include 
#include 

using namespace std;

int main()
{
	int a = 3;
	unordered_set us;
	unordered_set::iterator usit;
	cout << "===> before" << endl;
	cout << "us's size() is " << us.size() << endl;
	cout << "us's count(NULL) is " << us.count(NULL) << endl;
	usit = us.find(NULL);
	if (usit != us.end()) {
		cout << "usit can find NULL element" << endl;
	} else {
		cout << "usit can't find NULL element" << endl;
	}

	us.insert(NULL);

	cout << "===> after" << endl;
	cout << "us's size() is " << us.size() << endl;
	cout << "us's count(NULL) is " << us.count(NULL) << endl;
	
	usit = us.find(NULL);
	if (usit != us.end()) {
		cout << "usit can find NULL element" << endl;
	} else {
		cout << "usit can't find NULL element" << endl;
	}

	return 0;
}

===> before
us's size() is 0
us's count(NULL) is 0
usit can't find NULL element
===> after
us's size() is 1
us's count(NULL) is 1
usit can find NULL element

um对NULL的处理


#include 
#include 
#include 

using namespace std;

int main()
{
	int a = 3;
	unordered_map um;
	unordered_map::iterator umit;
	cout << "===> before" << endl;
	cout << "um's size() is " << um.size() << endl;
	cout << "um's count(NULL) is " << um.count(NULL) << endl;
	umit = um.find(NULL);
	if (umit != um.end()) {
		cout << "umit can find NULL element" << endl;
	} else {
		cout << "umit can't find NULL element" << endl;
	}

	um[NULL] = NULL;

	cout << "===> after" << endl;
	cout << "um's size() is " << um.size() << endl;
	cout << "um's count(NULL) is " << um.count(NULL) << endl;
	umit = um.find(NULL);
	if (umit != um.end()) {
		cout << "umit can find NULL element" << endl;
	} else {
		cout << "umit can't find NULL element" << endl;
	}
	return 0;
}

===> before
um's size() is 0
um's count(NULL) is 0
umit can't find NULL element
===> after
um's size() is 1
um's count(NULL) is 1
umit can find NULL element

你可能感兴趣的:(算法,leetcode,深度优先,算法)