字符串总结

待定

一.字符串基础      可参考: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. 交错字符串,给定三个字符串 s1s2s3,请你帮忙验证 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,且满足:

  1. 0 <= F[i] <= 2^31 - 1,(也就是说,每个整数都符合 32 位有符号整数类型);
  2. F.length >= 3;
  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];
    }

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值