⭐⭐⭐⭐⭐【LCA in BST+先序遍历理解】PAT A1143 Lowest Common Ancestor

题目描述

在这里插入图片描述

知识点

BST的遍历——遍历先序序列,就是在先序遍历树~

实现

码前思考

  1. 题中指明了给的整数是distinct的;
  2. 对于树中的每一个整数,我们使用unordered_set进行保存,从而可以直接判断是否存在于树中,否则查找会花费很大的时间复杂度;
  3. 我在第一次写的时候,测试点3,4超时,死活没有找出来原因,后来百度,发现原来是因为我建树的过程是一个一个 insert() 进行的,这种建树方法称为 在线建树 ,而如果直接使用前序序列一次性进行建树,则成为 离线建树 ,由于在线建树每次都会有大量的重复的查找操作,因此时间复杂度非常高,最坏为 O ( N 2 ) O(N^2) O(N2)!而离线建树的时间复杂度为 O ( N ) O(N) O(N),因此,以后还是使用离线建树比较好!
  4. 对于如何找到uvLCA,只需要从根结点开始寻找,如果出现了两个uv被分配到不同的子树上,那么当前结点就是LCA。看代码更加直接。

代码实现

//运行超时了 
//建树的一个要点,直接进行建树会多很多查找的时间复杂度!!!! 
#include "bits/stdc++.h"
using namespace std;

const int maxn = 1e4+10;

struct node{
	int val;
	node* l;
	node* r;
	node(int v):val(v),l(NULL),r(NULL){}
};

int m;
int n;
int data[maxn];
unordered_set<int> st; 

node* create(int preL,int preR){
	
	if(preL > preR){
		return NULL;
	}else{
		node* root = new node(data[preL]);
		int numLeft=0;
		for(int i=preL+1;i<=preR;i++){
			if(data[i] < root->val){
				numLeft++;
			}else{
				break;
			}
		}
		
		root->l = create(preL+1,preL+numLeft);
		root->r = create(preL+numLeft+1,preR);
		return root;
	}
} 

//共同点一定出现在某个分叉处 
void find(node* root,int u,int v,int& lca){
	if(root->val > u && root->val > v){
		find(root->l,u,v,lca);
	}else if(root->val < u && root->val < v){
		find(root->r,u,v,lca);
	}else{
		lca = root->val;
	}
	return; 
}


int main(){
	scanf("%d %d",&m,&n);

	
	for(int i=0;i<n;i++){
		scanf("%d",&data[i]);		
		st.insert(data[i]); 
	}
	
	node* root = NULL;
	root = create(0,n-1);
	
	//开始进行判断
	for(int i=0;i<m;i++){
		int u;
		int v;
		scanf("%d %d",&u,&v);
		if(st.find(u) == st.end() && st.find(v) == st.end()){
			printf("ERROR: %d and %d are not found.\n",u,v);
		}else if(st.find(u) == st.end() && st.find(v) != st.end()){
			printf("ERROR: %d is not found.\n",u);
		}else if(st.find(u) != st.end() && st.find(v) == st.end()){
			printf("ERROR: %d is not found.\n",v);
		}else{
			int lca;
			find(root,u,v,lca);
			if(lca == u){
				printf("%d is an ancestor of %d.\n",u,v);
			}else if(lca == v){
				printf("%d is an ancestor of %d.\n",v,u);
			}else{
				printf("LCA of %d and %d is %d.\n",u,v,lca);
			}			
		} 
		
	} 
	
	return 0;
}

码后反思

  1. 我之前还窃喜可以直接用先序遍历直接构建BST,结果这就栽了一个跟头,以后还是采用 离线的方式 算了;
  2. 柳神是直接利用了前序遍历序列的性质,都不用建树,实在是牛逼,佩服佩服。
    #include <iostream>
    #include <vector>
    #include <map>
    using namespace std;
    map<int, bool> mp;
    int main() {
        int m, n, u, v, a;
        scanf("%d %d", &m, &n);
        vector<int> pre(n);
        for (int i = 0; i < n; i++) {
            scanf("%d", &pre[i]);
            mp[pre[i]] = true;
        }
        for (int i = 0; i < m; i++) {
            scanf("%d %d", &u, &v);
            for(int j = 0; j < n; j++) {
                a = pre[j];
                if ((a >= u && a <= v) || (a >= v && a <= u)) break;
            } 
            if (mp[u] == false && mp[v] == false)
                printf("ERROR: %d and %d are not found.\n", u, v);
            else if (mp[u] == false || mp[v] == false)
                printf("ERROR: %d is not found.\n", mp[u] == false ? u : v);
            else if (a == u || a == v)
                printf("%d is an ancestor of %d.\n", a, a == u ? v : u);
            else
                printf("LCA of %d and %d is %d.\n", u, v, a);
        }
        return 0;
    }
    

二刷代码

本来想模仿柳神的代码,结果发现自己使用了递归,导致时间复杂度还是超了。。。
在这里插入图片描述
代码是这样子的:

//可以采用直接建树的方式么?但是这里想尝试一下直接基于数组的的方式,柳神貌似就是基于数组的 
//使用一个set来存储是否在树上?经过粗略得证明,这样应该是可行的! 
#include <iostream>
#include <unordered_set> 
using namespace std;

const int maxn = 1e4+10;

int m;
int n;
int pre[maxn];
unordered_set<int> st;
int u;
int v;

//递归方式 
void search(int idx,int bound){
	//递归边界
	if(pre[idx] == u){
		printf("%d is an ancestor of %d.\n",u,v);
		return;
	}else if(pre[idx] == v){
		printf("%d is an ancestor of %d.\n",v,u);
		return;		
	}else if(pre[idx] > u && pre[idx] < v || pre[idx] < u && pre[idx] > v){
		printf("LCA of %d and %d is %d.\n",u,v,pre[idx]);
		return;		
	}
	//寻找左右分水岭
	int left;
	int right=bound+1;//注意初始化,可能会出现右子树为空 
	for(int i=idx+1;i<=bound;i++){
		if(pre[i] < pre[idx]){
			left=i;
			break;
		}
	} 
	for(int i=idx+1;i<=bound;i++){
		if(pre[i] > pre[idx]){
			right=i;
			break;
		}
	}
	
	if(pre[idx] > u && pre[idx] > v){
		search(left,right-1);
	}else if(pre[idx] < u && pre[idx] < v){
		search(right,bound);
	}
}

int main(){
	//freopen("input.txt","r",stdin); 
	scanf("%d %d",&m,&n);
	for(int i=0;i<n;i++){
		scanf("%d",&pre[i]);
		st.insert(pre[i]);
	}
	
	for(int i=0;i<m;i++){
		scanf("%d %d",&u,&v);
		if(st.find(u)==st.end() && st.find(v)!=st.end()){
			printf("ERROR: %d is not found.\n",u);
		}else if(st.find(u)!=st.end() && st.find(v)==st.end()){
			printf("ERROR: %d is not found.\n",v);
		}else if(st.find(u)==st.end() && st.find(v)==st.end()){
			printf("ERROR: %d and %d are not found.\n",u,v);
		}else{
			search(0,n-1);
		}
	}
	return 0;
}

超时的原因出现在分水岭上,出现了遍历数组的情况!!!

柳神的思路其实就是:你在遍历pre数组的时候,你就是在先序遍历BST。(这个太重要了!!!)

这样你根据前面说的:在这里插入图片描述
这样问题就迎刃而解了,这样的结点只会有一个,那么你先序遍历时一定能找到这个节点!!!!

代码如下:

//可以采用直接建树的方式么?但是这里想尝试一下直接基于数组的的方式,柳神貌似就是基于数组的 
//使用一个set来存储是否在树上?经过粗略得证明,这样应该是可行的! 
#include <iostream>
#include <unordered_set> 
using namespace std;

const int maxn = 1e4+10;

int m;
int n;
int pre[maxn];
unordered_set<int> st;
int u;
int v;

int main(){
	//freopen("input.txt","r",stdin); 
	scanf("%d %d",&m,&n);
	for(int i=0;i<n;i++){
		scanf("%d",&pre[i]);
		st.insert(pre[i]);
	}
	
	for(int i=0;i<m;i++){
		scanf("%d %d",&u,&v);
		if(st.find(u)==st.end() && st.find(v)!=st.end()){
			printf("ERROR: %d is not found.\n",u);
		}else if(st.find(u)!=st.end() && st.find(v)==st.end()){
			printf("ERROR: %d is not found.\n",v);
		}else if(st.find(u)==st.end() && st.find(v)==st.end()){
			printf("ERROR: %d and %d are not found.\n",u,v);
		}else{
			int j;
			for(j=0;j<n;j++){
				if(pre[j] <=u && pre[j] >= v || pre[j] >= u && pre[j] <= v)
				break;
			}
			if(pre[j] == u){
				printf("%d is an ancestor of %d.\n",u,v);	
			}else if(pre[j] == v){
				printf("%d is an ancestor of %d.\n",v,u);
			}else{
				printf("LCA of %d and %d is %d.\n",u,v,pre[j]);
			}
		}
	}
	return 0;
}

最后的最后,我认为这道题目还是用我最原始的那种离线建树的方法是最好的!因为根据先序,中序,后序遍历建树才是机试中最常见的题目,柳神这种解法不普遍。

此外,对于BST我们要采用离线建树而不是insert()建树!insert()的时间复杂度太高了!、

这只是BST上的LCA,真实的LCA没这么简单!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值