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;
}
};