题目链接:CSL的字符串
题目描述:
CSL 以前不会字符串算法,经过一年的训练,他还是不会……于是他打算向你求助。
给定一个字符串,只含有可打印字符,通过删除若干字符得到新字符串,新字符串必须满足两个条件:
原字符串中出现的字符,新字符串也必须包含。
新字符串中所有的字符均不相同。
新字符串的字典序是满足上面两个条件的最小的字符串。
输入描述:
仅一行,有一个只含有可打印字符的字符串 s。
|s|≤10^5
输出描述:
在一行输出字典序最小的新字符串。
示例1
输入
bab
输出
ab
示例2
输入
baca
输出
bac
备注:
ASCII字符集包含 94 个可打印字符(0x21 - 0x7E),不包含空格。
解析:
题目描述的很清楚了,设不同字符的个数为n个,把字符串分成n个区间,输出每个区间中的某一个字符,这就是题目要求的删除,怎么分区间就是要按照字典序最小的这个条件了。第一步,要做到字典序最小,那么就要尽可能的增加每个区间的长度,因为足够长的区间才能比较出最小的字符。第二步,区间的限制需要满足题目要求的每个不同的字符都出现一次,所以当现在这个字符c是该字符串出现的最后一个字符c,这个区间的最大长度也就确定下来了,至于当前字符是否是最小的并不影响,如果是那么下一个区间是从这个字符之后开始,如果不是,那么下一个就是从那个最小的字符第一次在该区间出现的位置之后开始。
简单来说,用_i代表当前区间的开始位置,j代表s[j]字符是还未被选过的字符并且是它在字符串最后出现的位置,用_j代表该区间最小字符第一次出现的位置。理所应当的,下一个区间的起始位置是_j+1,并且s[_j]不会再参与最小字符以及最后出现位置的比较。
代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<cmath>
#include<map>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
map<char,int>mmp;//用来标记字符出现的次数
map<char,int>mmp2;//用来标记字符是否被选过
int main()
{
string s;
cin>>s;
int len=s.size();
for(int i=0;i<len;i++)
{
mmp[s[i]]++;//记录次数
}
int len2=mmp.size();
int _i=0;
for(int i=0;i<len2;i++)//len2就是不同字符的个数
{
int j;
int _j=-1;
for(j=_i;j<len;j++)
{
if(!mmp2[s[j]])//没被选过
{
if(_j==-1) _j=j;
else if(s[_j]>s[j]) _j=j;
mmp[s[j]]--;
if(mmp[s[j]]==0)//如果区间不在s[j]这里结束,就会永远失去输出s[j]的机会
{
break;
}
}
}
cout<<s[_j];//再_i到j区间内最小字符,并且是最靠前的
mmp2[s[_j]]++;//记录在已选中的部分,下次就会绕过
for(int k=_j+1;k<=j;k++)
{
if(!mmp2[s[k]])
mmp[s[k]]++;//把之前减掉的补回来
}
mmp[s[_j]]=0;
_i=_j;
}
return 0;
}