待定
一.字符串基础 可参考:C,C++字符串总结
0.string的构造函数
/**
string():构造空的string类对象,既空字符串
string(const char* s):使用C语言的字符串构造string类对象
string(size_t n,char c):构造后的string类对象包括n个字符c
string(const string& s):用string类对象s拷贝构造另一个对象
string(const string& s, size_t n):使用对象s中的第n个字符开始构造新的string类对象
**/
res=string(1,src[ptr++]); //字符转换成字符串
1.substring() 和substr()
str.substr(start,len); //若无第二个参数,直接截取到最后
2.char转string C++ char与string之间的转换
char ch='s';
string str(ch) //wrong,没有相关的构造函数
string str(1,ch) //string (size_t n, char c);
str.push_back(ch) //string 也是一种容器
3.字符串之cctype:判断字符类型,以及大小写转换
- 适用范围单个字符,常用文件已经包含
- 大小写转换,既可以自己实现单个字符转换,也可以使用transform(str.begin(),str.end(),str.begin(),tolower)
int isalpha(int ch) 若ch是字母('A'-'Z','a'-'z')返回非0值,否则返回0
int isalnum(int ch) 若ch是字母('A'-'Z','a'-'z')或数字('0'-'9')返回非0值,否则返回0
int isascii(int ch) 若ch是字符(ASCII码中的0-127)返回非0值,否则返回0
int iscntrl(int ch) 若ch是作废字符(0x7F)或普通控制字符(0x00-0x1F)返回非0值,否则返回0。
int isdigit(int ch) 若ch是数字('0'-'9')返回非0值,否则返回0
int isgraph(int ch) 若ch是可打印字符(不含空格)(0x21-0x7E)返回非0值,否则返回0
int islower(int ch) 若ch是小写字母('a'-'z')返回非0值,否则返回0
int isprint(int ch) 若ch是可打印字符(含空格)(0x20-0x7E)返回非0值,否则返回0
int ispunct(int ch) 若ch是标点字符(0x00-0x1F)返回非0值,否则返回0
int isspace(int ch) 若ch是空格(' '),水平制表符('\t'),回车符('\r'),走纸换行('\f')垂直制表符('\v'),换行符('\n')返回非0值,否则返回0
int isupper(int ch) 若ch是大写字母('A'-'Z')返回非0值,否则返回0
int isxdigit(int ch) 若ch是16进制数('0'-'9','A'-'F','a'-'f')返回非0值,否则返回0
int tolower(int ch) 若ch是大写字母('A'-'Z')返回相应的小写字母('a'-'z')
int toupper(int ch) 若ch是小写字母('a'-'z')返回相应的大写字母('A'-'Z')
//大小写转换
string str="sssss";
for (auto &ch : str) {
ch = toupper(ch);
}
cout << str << endl;
transform(str.begin(),str.end(),str.begin(),tolower); //头文件:algorithm
cout << str << endl;
4.string和int之间的转换:stoi(str)和to_string(num)
string str="123355";
int a=stoi(str);
string sss=to_string(a);
5.利用stringstream切割字符 stringstream的使用
- stringstream 默认是通过空格来识别不同的字符串
stringstream ss(data);
string tmp;
while(getline(ss,tmp,',')){ //istream& getline ( istream &is , string &str , char delim );
cout<<tmp<<endl;
}
6.string之append()于push_back()
- append()后面添加字符串,push_back()后面加字符
basic_string &append( const basic_string &str );
basic_string &append( const char *str );
basic_string &append( const basic_string &str, size_type index, size_type len );
basic_string &append( const char *str, size_type num );
basic_string &append( size_type num, char ch );
basic_string &append( input_iterator start, input_iterator end );
{
string s0 = "Hello ";
string s1 = "world!";
string s2 = "Hello World!";
s0.append(s1);
cout << s0 << endl; //Hello World!
s1.append(s2, 6, 6); // <str,loc,len>
cout << s1 << endl; //world!World!
s2.append(5, '!'); // <times,char>
cout << s2 << endl;//Hello World!!!!!!
}
6.字符串是否以‘\0'结尾:不是以它作为结束标志的,也不需要因为string是作为对象处理的;
{
//string a("abcd", 3);
string a = "abs";
cout << "---------------------" << endl;
cout << a.capacity() << endl; //15
cout << a.size() << endl; //3
if (a[3] == '\0') {
cout << "yes" << endl; //说明字符串结尾确实是'\0'
}
else {
cout << "no" << endl;
}
a[1] = '\0';
cout << a << endl; //说明在c++中,的确不是以'\0'为结尾的识别符号
}
二.字符串之查找(模糊查找,kmp)
1.字符串哈希
- 使用Rabin-Karp 字符串编码 ,将字符串进行哈希映射
例题1--1392. 最长快乐前缀,快乐前缀是在原字符串中既是 非空 前缀也是后缀(不包括原字符串自身)的字符串。给你一个字符串 s,请你返回它的 最长快乐前缀。如果不存在满足题意的前缀,则返回一个空字符串。
基本思路:使用Rabin-Karp 字符串编码 ,将字符串进行哈希映射,在本题目中,26个小写字母,映射到31进制中,并将其转换成十进制,就是其哈希值,根据哈希判断前缀是否相等。
string longestPrefix(string s) {
int base=31,mul=1; //31位进制
int mod=1e9+7,len=s.size(); //防止溢出
int happy=0;
int prefix=0,suffix=0;
for(int i=1;i<len;i++){
prefix=((long long)prefix*base+s[i-1]-'a')%mod;
suffix=(suffix+(long long)(s[len-i]-'a')*mul)%mod;
if(prefix==suffix){
happy=i;
}
mul=(long long )mul*base%mod;
}
return s.substr(0,happy);
}
三.字符串之应用
字符串之常用方法,可以优先考虑贪心,动态规划,实在不行可以考虑回溯+剪枝
- 如果求最大最小问题时,可以优先考虑贪心,动态规划,单调栈,归并
- 如果需要标记或者归档,可以使用哈希表,尤其都是大小写字母
例题1--49. 字母异位词分组,给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。 哈希表
基本思路:将不同的异位词组合在一起,显然是需要使用哈希表,但是key应该是什么?
- 思路1:排好序的字符串
- 思路2:统计各个字符出现的次数,标记为特殊形式,比如acbc表示为“1#1#2#”,或者表示为“abcc”,类似与排序,但是时间复杂度为O(n);
- 思路3:利用算数基本定理,使用质数表示各个字符,然后相乘,异位词的乘积必然相等,不是异位词的乘积必然不相等
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<double,vector<string>> dict;
int alp[26]={2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103};
vector<vector<string>> ans;
for(auto str:strs){
double t=1;
for(auto ch:str){
t*=alp[ch-'a'];
}
dict[t].push_back(str);
}
for(auto &it:dict){
ans.push_back(it.second);
}
return ans;
}
例题2--97. 交错字符串,给定三个字符串 s1
、s2
、s3
,请你帮忙验证 s3
是否是由 s1
和 s2
交错 组成的。 动态规划
基本思路:动态规划,dp[i][j]表示的是由S1的前i个字符和S2的前j个字符是否可以组成S3的前i+j个字符
- 对于dp[i][j],其最后一个字符,只能是来自于S1的第i个字符,或者S2的第j个字符
- dp[i][j]=( (s2[j-1]==s3[pos-1]) &&dp[i][j-1] || (s1[i-1]==s3[pos-1]&&dp[i-1][j]) )
注意:该问题类似于路径查找,依照下面的图(来自于leetcode 的sage),且只能向下或者向右,向右相当于选择S2[j],相下相当于选择S1[i],
bool isInterleave(string s1, string s2, string s3) {
int m=s1.size(),n=s2.size(),k=s3.size();
if(m+n!=k)
return false;
if(m*n==0)
return s1+s2==s3;
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
int pos=0;
for(int j=1;j<=n&&s3[j-1]==s2[j-1];j++){
dp[0][j]=1;
}
for(int i=1;i<=m&&s3[i-1]==s1[i-1];i++){
dp[i][0]=1;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
pos=i+j;
if(s2[j-1]==s3[pos-1]&&dp[i][j-1]){ //只和前面的状态有关,可压缩
dp[i][j]=1;
}
if(s1[i-1]==s3[pos-1]&&dp[i-1][j]){
dp[i][j]=1;
}
}
}
return dp[m][n];
}
例题3--132. 分割回文串 II,给给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。返回符合要求的最少分割次数。 动态规划
基本思路:动态规划,dp[i]表示0~i字符可以切割成回文子串的最小切割个数
- dp[i]=min(dp[i],dp[j]+1) ,if(s[j+1:i]是回文串)
- 判断是否是回文串,也用到了动态规划,iaPal[i][j]表示的是s[i:j]是否是回文串
- isPal[i][j]=(s[left]==s[right]&&(right-left<3||isPal[left+1][right-1]))
int minCut(string s) {
if(s.size()<2)
return 0;
vector<int> dp(s.size(),0);
vector<vector<bool>> isPal(s.size(),vector<bool>(s.size(),false)); //是否是回文串
for(int right=0;right<s.size();right++){
for(int left=0;left<=right;left++){
if(s[left]==s[right]&&(right-left<3||isPal[left+1][right-1]))
isPal[left][right]=true;
}
}
for(int i=0;i<s.size();i++){
dp[i]=i; //初始化,i+1个字符,最多需要分割i次;
if(isPal[0][i]){
dp[i]=0;
continue;
}
for(int j=1;j<=i;j++){
if(isPal[j][i]){
dp[i]=min(dp[i],dp[j-1]+1);
}
}
}
return dp[s.size()-1];
}
例题4--842. 将数组拆分成斐波那契序列, 回溯+剪枝
给定一个数字字符串 S,比如 S = "123456579",我们可以将它分成斐波那契式的序列 [123, 456, 579]。形式上,斐波那契式序列是一个非负整数列表 F,且满足:
- 0 <= F[i] <= 2^31 - 1,(也就是说,每个整数都符合 32 位有符号整数类型);
- F.length >= 3;
- 对于所有的0 <= i < F.length - 2,都有 F[i] + F[i+1] = F[i+2] 成立。
另外,请注意,将字符串拆分成小块时,每个块的数字一定不要以零开头,除非这个块是数字 0 本身。返回从 S 拆分出来的任意一组斐波那契式的序列块,如果不能拆分则返回 []。
基本思路:字符串拆分,没有明显的思路,可以使用回溯的算法,在回溯的过程中要注意剪枝,下面的是在查找合适的F[i+2]的过程中可以剪枝的地方:
- 当F[i+2]>F[i+1]+F[i],回溯终止,因为后面的F[i+2]会更大
- 如果F[i+2]以0开始,那么只能为零,后面的直接pass掉
- 当F[i+2]>INT_MAX,也直接pass掉
bool backtrack(string &s,int start,vector<int> &ans){
if(start==s.size()){
return ans.size()>=3;
}
long long cur=0;
for(int i=start;i<s.size();i++){
cur=cur*10+s[i]-'0';
if(cur>INT_MAX) //剪枝
break;
if(s[start]=='0'&&i>start) //剪枝
break;
if(ans.size()>=2){
//long long sum=ans[ans.size()-1]+ans[ans.size()-2]; //溢出
long long a=ans[ans.size()-1];
long long b=ans[ans.size()-2];
long long sum=a+b;
if(cur>sum) //cur>sum 剪枝
break;
if(cur<sum)
continue;
}
ans.push_back(cur);
if(backtrack(s,i+1,ans)){ //当都不成功时,回溯致使ans为空
return true;
}
ans.pop_back();
}
return false;
}
vector<int> splitIntoFibonacci(string s) {
vector<int> ans;
backtrack(s,0,ans);
return ans;
}
例题5--316. 去除重复字母,给你一个字符串 s
,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。 哈希表+单调栈
基本思路:无重复字母且最小,理想情况下,单调递增的字符组成的字符串最小,可以考虑单调栈,删除逆序且后面还会出现的字符,使其尽量单调递增
- 怎么最小?无重复字符串,单调递增时最小;采用何种结构?栈
- 何时弹出?遇到当前元素比栈顶元素小且栈顶元素在后面还有的情况下,弹出栈顶元素。
- 如何判断栈顶元素在后面还有?两种思路:
- 思路一:哈希表统计各个元素数量,每遍历一个元素减1,若大于零,则表示后面还有
- 思路二:哈希表统计该字母最后一次出现的位置
//思路一
string removeDuplicateLetters(string s) {
vector<int> dict(26,0);
vector<bool> inst(26,false); //标记是否访问过
string ans="";
for(auto &ch:s){
dict[ch-'a']++;
}
stack<char> stk;
for(int i=0;i<s.size();i++){
char ch=s[i];
if(inst[ch-'a']){
dict[ch-'a']--;
continue;
}
while(!stk.empty()&&s[i]<stk.top()&&dict[stk.top()-'a']>0){
inst[stk.top()-'a']=false;
stk.pop();
}
stk.push(ch);
dict[ch-'a']--;
inst[ch-'a']=true;
}
while(!stk.empty()){
ans=stk.top()+ans;
stk.pop();
}
return ans;
}
例题6--301. 删除无效的括号,删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。说明: 输入可能包含了除 ( 和 ) 以外的字符。
基本思路:若无明显的思路,可以试着使用dfs/bfs;
- dfs,首先遍历字符串,记录不合法左右括号数,限制遍历的深度,然后在dfs过程中,若前后两个字符相同,则可以直接跳过,防止重复
bool isValid(string &str){
int cnt=0;
for(char &ch:str){
if(ch=='('){
cnt++;
}
if(ch==')'){
cnt--;
if(cnt<0)
return false;
}
}
return cnt==0;
}
void dfs(string s,vector<string> &ans,int start,int left,int right){
if(left==0&&right==0){
if(isValid(s)){
ans.push_back(s);
}
return ;
}
for(int i=start;i<s.size();i++){
if(i>start&&s[i]==s[i-1])
continue; //去重
if(left>0&&s[i]=='('){
dfs(s.substr(0,i)+s.substr(i+1,s.size()-i-1),ans,i,left-1,right);
}
if(right>0&&s[i]==')'){
dfs(s.substr(0,i)+s.substr(i+1,s.size()-i-1),ans,i,left,right-1);
}
}
}
vector<string> removeInvalidParentheses(string s) {
int left=0,right=0;
for(char &ch:s){
if(ch=='('){
left++;
}
if(ch==')'){
if(left>0){
left--;
}
else{
right++;//记录要删除的左右括号数目
}
}
}
vector<string> ans;
dfs(s,ans,0,left,right);
return ans;
}
- BFS,从上往下遍历,当到达最小删除括号数目层时,停止遍历。
def isValid(str):
cnt=0;
for ch in str:
if ch=='(':
cnt+=1
if ch==')':
cnt-=1
if cnt<0:
return False
return cnt==0
level={s}
while True:
valid=list(filter(isValid,level)) #filter(function, iterable),python3返回的是类,需要转换成list
if valid: return valid
next_level=set()
for item in level:
for pos in range(len(item)):
if item[pos] in "()":
next_level.add(item[:pos]+item[pos+1:])
level=next_level
例题7--构造最大数,给定一个只包含正整数的数组,给出一个方法,将数组中的数拼接起来,使得拼接后的数最大。例如,[1, 32,212]拼接之后,所得到的最大数为322121。
基本思路:贪心算法,每次拼接都选取当前字典序最大的数:
- 按照字典从小到大的顺序排序,如果出现一个是另一个字符串子串的问题,可以将两个字符串a,b相加,在比较ab和ba的大小
bool compare(int a, int b) {
string str_a = to_string(a);
string str_b = to_string(b);
int i = 0;
while (i < str_a.size() && i < str_b.size()) {
if (str_a[i] != str_b[i]) {
return str_a[i] > str_b[i];
}
i++;
}
return str_a + str_b > str_b + str_a; //注意一个是另外一个的子串的问题
}
string getMaxNum(vector<int> &nums) {
stringstream ss;
string ans;
sort(nums.begin(), nums.end(), compare);
for (auto &it : nums) {
ss << it;
}
ss >> ans;
return ans;
}
例题8--剑指 Offer 19. 正则表达式匹配,请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。
基本思路: 字符串匹配问题,可以使用动态规划,注意p[j]=='*'这种特殊情况
- 若p[j-1]=='*',则p[j-2]这个字符可以重复0,1,多次;分别对应dp[i][j-2],dp[i][j-1],dp[i-1][j]
bool isMatch(string s, string p) {
int len1=s.size();
int len2=p.size();
vector<vector<bool>> dp(len1+1,vector<bool>(len2+1,false));
dp[0][0]=true;
for(int j=2;j<=len2;j+=2){ //注意第零行的初始化
if(p[j-1]=='*'&&dp[0][j-2])
dp[0][j]=true;
else
break;
}
for(int i=1;i<=len1;i++){ //s
for(int j=1;j<=len2;j++){ //p
if(s[i-1]==p[j-1]||p[j-1]=='.')
dp[i][j]=dp[i-1][j-1];
else if(j>=2&&p[j-1]=='*'){
if(p[j-2]=='.'||(p[j-2]==s[i-1]))
dp[i][j] =dp[i][j-1] || dp[i-1][j]; //d[i][j-1] 表示1个字符,dp[i-1][j]表示重复多个字符
dp[i][j] = dp[i][j] || dp[i][j-2]; //dp[i][j-2] 代表重复0个字符
}
}
}
return dp[len1][len2];
}