七月集训(25,26)树状数组,并查集

1.LeetCode:面试题 17.07. 婴儿名字

原题链接


        每年,政府都会公布一万个最常见的婴儿名字和它们出现的频率,也就是同名婴儿的数量。有些名字有多种拼法,例如,John 和 Jon 本质上是相同的名字,但被当成了两个名字公布出来。给定两个列表,一个是名字及对应的频率,另一个是本质相同的名字对。设计一个算法打印出每个真实名字的实际频率。注意,如果 John 和 Jon 是相同的,并且 Jon 和 Johnny 相同,则 John 与 Johnny 也相同,即它们有传递和对称性。

        在结果列表中,选择 字典序最小 的名字作为真实名字。

        示例:

        输入:names = [“John(15)”,“Jon(12)”,“Chris(13)”,“Kris(4)”,“Christopher(19)”], synonyms = [“(Jon,John)”,“(John,Johnny)”,“(Chris,Kris)”,“(Chris,Christopher)”]

        输出:[“John(27)”,“Chris(36)”]

        提示:

        names.length <= 100000


        比较简单的一道题目,这里偷个懒就直接利用umap来构造string到string的并查集,再用一个umap来构造string到int的关系,在union的时候我们可以手动控制让字典序小的来充当集合中的代表元素,首先通过synoyms数组构造并查集,然后利用第二个umap,遍历names,累加代表元素的频率即可。

class Solution {
    unordered_map<string,string> fa;
    unordered_map<string,int> n2f;
    
    string find(string name){
        return fa.count(name)==0?name:fa[name]=find(fa[name]);
    }

    void union_set(string &x,string& y)
    {
        string fx=find(x);
        string fy=find(y);
        if(fx!=fy){
            if(fx<fy){
                fa[fy]=fx;
            }else fa[fx]=fy;
        }
    }

    int s2i(string & s){
        int ans=0;
        int flag=s[0]=='-'?-1:1;
        for(int i=0;s[i];++i){
            if(s[0]<'0'||s[0]>'9') continue;
            ans=ans*10+s[i]-'0';
        }
        ans*=flag;
        return ans;
    }

    string i2s(int i){
        string ans;
        bool flag=false;
        if(i<0){
            flag=true;
            i*=-1;
        }
        while(i){
            ans.push_back(i%10+'0');
            i/=10;
        }
        if(flag) ans.push_back('-');
        reverse(ans.begin(),ans.end());
        return ans;
    }

public:
    vector<string> trulyMostPopular(vector<string>& names, vector<string>& synonyms) {
        vector<string> ans;
        for(auto &s:synonyms){
            int pos=s.find(',');
            string a=s.substr(1,pos-1);
            string b=s.substr(pos+1,s.size()-pos-2);
           // cout<<a<<" "<<b<<endl;
           union_set(a,b);
        }

        for(auto & s:names){
            int pos=s.find('(');
            string tmp=s.substr(pos+1,s.size()-pos-2);
            int score=s2i(tmp);
            string name=s.substr(0,pos);
            //cout<<name<<" "<<score<<endl;
            string fa=find(name);
            //cout<<fa<<endl;
            n2f[fa]+=score;
        }

        for(auto& [name,score]:n2f){
            string cnt=i2s(score);
            string s=name+"("+cnt+")";
            //cout<<s<<endl;
            ans.push_back(s);
        }

        return ans;
    }
};

2.LeetCode:327.区间和的个数

原题链接


        给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数 。

        区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。

        示例 1:

        输入:nums = [-2,5,-1], lower = -2, upper = 2

        输出:3

        解释:存在三个区间:[0,0]、[2,2] 和 [0,2] ,对应的区间和分别是:-2 、-1 、2 。

        示例 2:

        输入:nums = [0], lower = 0, upper = 0

        输出:1

        提示:

        1 <= nums.length <= 1e5

        -2147483648 <= nums[i] <= 2147483647

        -1e5 <= lower <= upper <= 1e5

        题目数据保证答案是一个 32 位 的整数


        如果看出来我们要查找的数据就会发现本题是一个树状数组模板题,但是如果看不出来的话本题就是纯纯困难题了。

        一个区间[L,R]他的区间和是pre[r]-pre[i-1],固定r,如果该区间和满足条件就有 pre[r]-upper<=pre[l]<=pre[r]-lower,也就是说我们要找的其实就是在r下标之前的前缀和位于[pre[r]-upper,pre[r]-lower]的元素个数.

        那么这就是很经典的单点修改区间查询的题目了,同时也是树状数组的经典运用。遍历数组,然后利用树状数组查找其对应区间的元素个数即可,由于这里存在负数,所以需要对所有用到的元素进行离散化操作,也就是pre[i],pre[i]-upper,pre[i]-lower。

class Solution {
    typedef long long ll;
    #define maxn 100010
    int tr[maxn*3];
    int n;
    int lowbit(int x){
        return x&-x;
    }
    void update(int x){
        while(x<=n){
            tr[x]++;
            x+=lowbit(x);
        }
    }
    int query(int x){
        int ans=0;
        while(x>0){
            ans+=tr[x];
            x-=lowbit(x);
        }
        return ans;
    }
    ll pre[maxn];
public:
    int countRangeSum(vector<int>& nums, int lower, int upper) {
        int ans=0;
        n=nums.size();
        for(int i=1;i<=n;++i){
            pre[i]=pre[i-1]+nums[i-1];
        }
        set<ll> s;
        for(int i=0;i<=nums.size();++i){
            s.insert(pre[i]);
            s.insert(pre[i]-lower);
            s.insert(pre[i]-upper);
        }
        unordered_map<ll,int> map;
        int cnt=0;
        for(auto i:s){
            map[i]=cnt++;
        }
        n=map.size();
        for(int i=0;i<=nums.size();++i){
            int l=map[pre[i]-upper],r=map[pre[i]-lower];
            ans+=query(r+1)-query(l);
            update(map[pre[i]]+1);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值