Map容器&用map优化程序

目录

基础知识及操作

基础知识

增删改查

map元素的访问

 map的遍历

题目练习

1.同构字符串

2.解码字母到整数映射 

3.​​​​​​完美K倍子数组​​​​​-计蒜客

4.和为K的子数组 

5.叫号系统-计蒜客

 小结


基础知识及操作

今天学习了map的用法,在之前的寒假训练时就接触到过,现在系统的来复习一遍加深记忆。

基础知识

映射:map:

  1. map<string,int> mp;                                                            将string映射到int中 初值为0

  2. 对于map来说,一个键只能映射一次,并且具有互异性

  3. map具有中括号运算符可以像数组一样进行运算                 如mp[键]

增删改查

        查找元素:

if(mp.find(要查找的元素)!=mp.end())

        如果找到返回此迭代器没有找到就返回尾迭代器,也可以用mp.count(键名)来查找因为map中键具有互异性所以有的话就只会返回1。


         删除元素:

map.erase(键---类似于数组下标)

          查大小---清空----判空

mp.size();
map.clear();
mp.empty();

 map会自动进行排序(而且是按照键的从小到大进行排序),所以如果对顺序没有要求的话尽量使用unordered_map,因为使用map在每次增加元素或删除元素时都会排序,导致浪费很多时间,用unordered_map可以省去很多时间!

map元素的访问

如果是通过对象本身进行访问的话就需要用到  .  来访问元素。

如果是通过迭代器进行访问的话就需要用到 -> 来访问元素。

 map的遍历

如果使用迭代器进行遍历的话,同样有两种方法:

map<int,int> mp;
	mp[1] = 1;
	mp[3] = 3;
	mp[5] = 5;
	for(auto i = mp.begin();i!=mp.end(); i++)
	{
		cout<<i->first<<' '<<i->second<<endl;
	}

这样就需要用 -> 因为当前的i是一个迭代器,通过迭代器访问元素的话就需要用->来访问

而你也可以用快速遍历(对编译器有要求):

for(auto &随便命名 : 容器名)
	{
		cout<<命的名<<endl;-----由于映射map输出时有两个所以应该写为
		cout<<名字.first<<' '<<名字.second<<endl;
    }
	

 下面就是今天练习的题目了:

题目练习

1.同构字符串

本来这道题就是一个简单的模拟题,但是好久没写代码的我竟然错了好几发找不到正确的思路,还是记下来熟悉一下count的用法吧,当然也可以用find != end() 来代替。

核心代码:

class Solution {
public:
    bool isIsomorphic(string s, string t) {
        //由于字符串S和T的长度相同 所以无序再进行判断
        unordered_map<char,char> mps,mpt;
        for(int i=0;i<s.length();i++)
        {
            if(mps.count(s[i]) && mps[s[i]] != t[i]) return false;
            if(mpt.count(t[i]) && mpt[t[i]] != s[i]) return false;
            mps[s[i]] = t[i];
            mpt[t[i]] = s[i];
        }
        return true;
    }
};

2.解码字母到整数映射

其实没必要用大量的函数(像一些stoi(),先转成字符串再拼接之类的 有点浪费时间 可以直接通过字符的ASCII码来计算数值)

class Solution {
public:
    string freqAlphabets(string s) {
        string ans;
        for(int i=s.length()-1;i>=0;i--)
        {
            if(s[i]>='1' && s[i]<='9') ans += 'a'+(s[i]-'0'-1);
            else if(s[i] == '#')
            {
                int num = (s[i-2]-'0');
                num*=10;
                num += (s[i-1]-'0');
                i-=2;
                ans += 'a'+num-1;
            }
        }
        reverse(ans.begin(),ans.end());
        return ans;
    }
};

当然也可以正向遍历,用i+2来查询字符 '#' 。

还有就是巩固reverse函数的使用。 

string ans;
reverse(ans.begin(),ans.end());

3.​​​​​​完美K倍子数组​​​​​-计蒜客

当我看到题目中的子数组时,我以为是必须是原数组中连续的一段,我还以为要用滑动窗口作,可是想了半天不知道用滑动窗口怎么实现,跟数值的大小没有关系,好像滑动窗口不能用。

滑动窗口通常适用于连续子数组的和、最值或特定范围问题,但无法直接处理“模运算”条件(即和为K的倍数)

后来看样例发现可以不连续,呢么就可以换一种思路来求解了:

要想数组中的元素两两相加的和都是K的倍数,那么我们可以讨论有几种情况能实现。

在这之前,我们可以讨论有几种情况使得两个数相加等于K,然后再进行取模分析。

不难发现,我们可以通过{k,0},{k/2,k/2},{x,k-x}三种情况,推广到取模时:

1-数组中所有元素都是K的倍数,这样不管是哪两个加起来都会是K的倍数,这种情况适用于不管K是奇数还是偶数的所有情况。

2-如果所有元素都对K取模等于K/2,那么 相加起来还是K的倍数(只适用于K为偶数的情况,因为只有K为偶数时,K/2才一样,如果不一样的话,比如K=5,分成2+3,这样就只能是两个元素作为一个数组,因为不管加进来哪个数,都会最多和其中一个数匹配而与另一个数无法匹配,也就是第三种情况)。

3-只有两个数为一组的如{2,3}。

如果子数组可以不连续的话,直接跑一遍统计一下即可,最终结果取得三种情况的最大值。

详解看代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define PII pair<int,int>
#define fi first
#define se second
const int N = 1e5+10;
int a[N];
int n,k;

void solve()
{
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	bool ff=0;//作为标记,表示是否能找到最少两个为一组的合法数组作为答案
	unordered_map<int,bool> mp;
	int ans1=0,ans2=0;
	for(int i=1;i<=n;i++)
	{
		if(a[i] % k == 0)//将所有K的倍数都统计起来
		{
			ans1++;
		}
		if(k%2==0 && a[i] % k * 2==k)//只有K为偶数时才需要考虑统计
		{
			ans2++;
		}
		if(!ff)//目前还没有找到两个为一组的合法答案
		{
			int t = a[i] % k;//取模便于操作
			if(mp[k - t]) ff = 1;//如果有需要的另一个数,就找到了
			mp[t] = 1;//不断统计之前的数
		}
	}
	int res = max(ans1,ans2);//取最大值
	if(res >= 2) cout<<res<<endl;//有大于两个的最优解,不管是否找到那个“二元组”,已经有了最优解 等于可加可不加 反正答案是2 具体是哪个二元组无需考虑
	else
	{
		if(ff) cout<<2<<endl;
		else cout<<-1<<endl;
	}
}

signed main()
{
	IOS
	int T=1;
//	cin>>T;
	while(T--) solve(); 
	return 0;
} 

类似题:

4.和为K的子数组

注意这道题中的子数组是指原数组中连续且非空的一段

而这个虽然连续但是也不用滑动窗口,原因是:

正解:

前缀和 + 哈希表:
不断的更新前缀和,因为要求一段连续的区间和为K,那么就是sum[r] - sum[l-1] = K,也就是sum[r] - K = sum[l-1],即找有几个sum[l-1]。所以遍历一遍,不断统计前缀和并用map存起来,表示到目前为止有的sum[l-1]的数量即类型信息,如果在遍历过程中出现了有需要的sum[l-1],那么只需要累加起来即可,有几个sum[l-1]就说明L到R的这段区间内有几个和为K且以R为右端点的新区间,所以要累加。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        mp[0] = 1;//初始化!!!为了找到第一个解
        int count = 0, pre = 0;
        for (auto& x:nums) {
            pre += x;
            if (mp.find(pre - k) != mp.end()) {
                //看他前面有几个能满足前缀和为pre-k的
                count += mp[pre - k];
            }
            mp[pre]++;//不要忘记也将前缀和累加起来
        }
        return count;
    }
};

map的综合应用:

5.叫号系统-计蒜客

这道题有点长,其实也就是一些相关的map的操作,需要注意的坑是要根据实际情况进行删除,不要漏删!

很明显是要用双向的map来存id对urg的映射以及urg对id的映射(由于操作中需要访问最值,所以不用unordered_map,直接用可以自动排序的map方便)。

#include <bits/stdc++.h>
using namespace std;
#define int long long 
#define endl '\n'
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define PII pair<int,int>
#define fi first
#define se second
map<int,int> id,urg;	//map可以自动排序

void solve()
{
	int n; cin>>n;
	while(n--)
	{
		int op;cin>>op;
		if(op==1)
		{
			int x,y;
			cin>>x>>y;
			id[x] = y;
			urg[y] = x;
		}
		if(op==2)
		{
			if(urg.empty()) cout<<"error"<<endl;
			else
			{
				cout<<urg.begin()->se<<endl;//通过迭代器访问用箭头
				id.erase(urg.begin()->se);//从系统中删除这个人的信息就要将两个map中都删除 千万不能忘记
				urg.erase(urg.begin());
			}
		}
		if(op==3)
		{
			if(urg.empty()) cout<<"error"<<endl;
			else
			{
				cout<<(--urg.end())->se<<endl;
				id.erase((--urg.end())->se);
				urg.erase(--urg.end());
			}
		}
		if(op==4)
		{
			int x,y;cin>>x>>y;//输入顺序别搞混
			urg.erase(id[x]);//因为要对id为x的人修改其urg为y,所以要进行3个操作:
			id[x] = y;//1.将id对应的值改为y	2.在urg中增加对应的urg和id信息
			urg[y] = x;//3.千万不要忘记把之前的urg删除 防止对后来的信息产生影响即冲突
		}
		if(op==5)
		{
			int x,y;cin>>x>>y;//与操作4一样
			id.erase(urg[y]);
			urg[y] = x;
			id[x] = y;
		}
		if(op==6)
		{
			int x;
			cin>>x;
			if(id.find(x) == id.end()) cout<<"error"<<endl;
			else cout<<id[x]<<endl;
		}
		if(op==7)
		{
			int y;
			cin>>y;
			if(urg.find(y) == urg.end()) cout<<"error"<<endl;
			else cout<<urg[y]<<endl;
		}
	}
}

signed main()
{
	IOS
	int T=1;
//	cin>>T;
	while(T--) solve(); 
	return 0;
} 

 小结

分清用 -> 还是用

map的end()返回的是最后一个元素后面的一个迭代器

所以使用find时,直接mp.find() != mp.end()即可。

访问最后一个元素是要用(--mp.end())->fi/se「迭代器类型用箭头!」

今天是第四天,感觉比前两天轻松了,起码没有学习什么新的东西,趁有时间再去复习复习!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值