Leetcode笔记之字符串

未完待续…

string s="";//初始化

并且要防止越界!!!!!

一.字符串简单题型

8.字符串转整数 (atoi)

1.定义 i=0;while 跳过空格
2.if 跳过+/-,确定符号sign;并且符号之后必须紧跟数字,否则返回0
3.溢出判断:如果当前res> INT_MAX / 10 或者res == INT_MAX / 10 && str[i] - ‘0’ > 7:sign为1返回INT_MAX,否则返回 INT_MIN。
4.res累积:res = 10 * res + (str[i++] - ‘0’);

int myAtoi(const char* str) {
    if(!str)
        return 0;
    int sign = 1, res = 0, i = 0;
    while (str[i] == ' ')  i++; //1.跳过空格
    if (str[i] == '-' || str[i] == '+') //2.+/-跳过,确定符号
    {
        if (str[i] == '-' ) sign=-1;
        ++i;
    }
    while (str[i] >= '0' && str[i] <= '9')//3.符号之后必须紧跟数字,否则返回0
    {
        if (res >  INT_MAX / 10 || (res == INT_MAX / 10 && str[i] - '0' > 7)) {//4.溢出判断
            if (sign == 1) 
                return INT_MAX;
            else 
                return INT_MIN;
        }
        res  = 10 * res + (str[i++] - '0');//5.计算
    }
    return res * sign;
}
28.实现strStr()

给定一个 S1 字符串和一个s2 字符串,在 S1字符串中找出s2字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
1.计算字符串长度的函数:int str_length(const char* str);
2.KMP算法的next计算函数:void Next(char* str,int* next,const int n);
3.if(k==len2) return i-lens2+1;

int str_length(const char* str)
{
    if(!str)
        return 0;
    int i=0;
    while(*str++ != '\0') ++i;
    return i;  
}
void Next(char* str,int* next,const int n)
{
    if(!str)
        return;
    next[0]=0;
    for(int i=1,k=0;i<n;++i)
    {
        while(k>0 && str[i]!=str[k])
            k=next[k-1];
        if(str[i]==str[k])
            ++k;
        next[i]=k;
    }
}
int strStr(char* haystack, char* needle) {
    if(!needle || !haystack)
        return -1;
    if(*needle=='\0')
        return 0;
    int len1=str_length(haystack);
    int len2=str_length(needle);
    int* next=(int*)malloc(len2*sizeof(int));
    Next(needle,next,len2);
    for(int i=0,k=0;i<len1;++i)
    {
        while(k>0 && haystack[i]!=needle[k])
            k=next[k-1];
        if(haystack[i]==needle[k])
            ++k;
        if(k==len2){
            free(next);
            return i-len2+1;
        }     
    }
    return -1;
}
3. 无重复字符的最长子串

1.申请一个hash表,保存当前字符的上一次出现的位置,变量pre记录截止到上一个字符的最长无重复字串的长度,申请一个数组dp[n],保存截止到当前位置的最长无重复字串的长度。
2.for循环遍历字符串,如果当前字符在hash中:
如果上一次出现的位置在i-pre和i之间,即M[s[i]] >= i-pre,则截止到当前位置的最长无重复字串长度为上次出现位置和本次为止的差:dp[i]=i-M[s[i]];;否则长度+1:dp[i]=++pre;
3.如果当前字符不在hash中,长度直接+1:dp[i]=++pre;

14.最长公共前缀

1.每次比较一个字符(在strs[0]中的位置为index),然后把这个字符和strs中其余字符串的对应index处的字符比较,相等则prefix加上这个字符然后继续;
2.否则返回当前prefix(加字符之前)。注意idx >= strs[i].size()时表示即将越界,直接返回当前prefix

string longestCommonPrefix(vector<string>& strs) {
    string prefix = "";
    int n=strs.size();
    if (n<=0)
        return prefix;
    //prefix每次多一个strs[0]中的字符:d,do,dog;对于strs[0]中的每一个字符的下标idx
    for(int idx=0; idx<strs[0].size(); prefix+=strs[0][idx], idx++)
        for(int i=0; i<n; i++)//对于strs中的每一个字符串
            //如果idx不小于当前字符串strs[i]中字符个数 或者 strs[i]中当前字符与strs[0]中当前对应字符串不一样,则返回prefix
            if(idx >= strs[i].size() ||(i > 0 && strs[i][idx] != strs[0][idx]))
                return prefix;
    return prefix;
}
17.电话号码的字母组合

res.push_back(“”)初始化
1.申请一个数组,for循环对于digits中的每一个数字,得到其索引用来访问v,判断合法输入,得到v中对应的字符串vi(const string& vi = v[num]), 如果0和1位置为空字符串则放弃本轮;

static const vector<string> v = {"", "", "abc", "def", "ghi",\
 "jkl", "mno", "pqrs", "tuv", "wxyz"};//1.对应0~9

2.每次迭代的结果保存在tmp,可以发现,随着得到每一轮的结果,res中字符串组合的个数是不断增多的,最后要交换res和tmp的值从而更新res;

vector<string> tmp;
for(int j = 0; j < vi.size(); ++j)//4.1 对于vi中的每个字符
    for(int k = 0 ; k < res.size() ; ++k)//4.2 对于res中的每个字符串
        tmp.push_back(res[k] + vi[j]);//4.3 res中的每个字符串+vi中的每个字符
swap(res,tmp);//5.交换res和tmp
20. 有效的括号

1.for(auto i : s),对于s中的每一个括号,如果st为空,直接入栈;
2.如果st不为空,即将插入的括号和st.top()的括号恰好匹配,则弹出栈顶;否则入栈。

bool isValid(string s) {
    stack<char> st;
    for(auto i : s)
    {
        if(!st.empty())
        {
            //如果即将插入的括号和st.top()的括号恰好匹配,则弹出栈顶,否则入栈
            if((st.top() == '{' && i == '}') || (st.top() == '[' && i == ']') || (st.top() == '(' && i == ')') )       
                st.pop();
            else                                    
                st.push(i);
        }
        else//st为空,直接入栈                                        
            st.push(i);
    }
    return st.empty();
}
22.括号生成

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
1.addingpar(vector< string > &v, string str, int n, int m);
//res, 初始化str(字符串这为了+,总是初始化为”“), 当前可添加的左括号个数,当前可添加的右括号个数;
2.1 m,n都为0,表示不可再添加
if(n==0 && m==0) {v.push_back(str);,return;}
2.2 添加左括号,同时可添加的左括号个数-1,右括号个数相应+1
addingpar(v, str+”(“, n-1, m+1);
2.3 添加右括号,同时可添加的左括号个数不变,右括号个数相应-1
addingpar(v, str+”)”, n, m-1);

vector<string> generateParenthesis(int n) {
    vector<string> res;
    addingpar(res, "", n, 0);//
    return res;
}
void addingpar(vector<string> &v, string str, int n, int m){
    if(n==0 && m==0) //m,n都为0,表示不可再添加
    {
        v.push_back(str);
        return;
    }
    if(n > 0)
        addingpar(v, str+"(", n-1, m+1); //添加左括号,同时可添加的左括号个数-1,,右括号个数相应+1
    if(m > 0)
        addingpar(v, str+")", n, m-1); //添加右括号,同时可添加的左括号个数不变,,右括号个数相应-1

}
58.最后一个单词的长度

和求第一个单词的长度解法一样
法一:从后往前遍历,去掉后面的空格,开始计数就可以了,直到又遇到了空格,返回res即可。和找第一个单词的长度一样的方法
法二:先反转s,然后求第一个单词的长度(从第一个非空格开始计数,++res;,直到下一个空格停止遍历,返回res即可)

int len = 0, i = s.size() - 1;
while (i >= 0 && s[i] == ' ') i--;
while (i >= 0 && s[i] != ' ') { len++; i--; }
return len;

二.反转与翻转

344.反转字符串c实现
char* reverseString(char* s) {
    int i=0;
    int len=strlen(s);
    int j=len-1;
    while(i<=j)
    {
        char tmp=s[i];
        s[i++]=s[j];
        s[j--]=tmp;
    }
    return s;
}
541. 反转字符串 II
string reverseStr(string s, int k) {
    int n= s.size();
    if(!n || k<2)//k==1时明显不用任何更改
        return s;
    for (int i = 0; i < n; i += 2 * k)   //2*k个一组,反转前k个
        s=reverse(s,i,i+k-1,n);
    return s;
}
string reverse(string s,int i,int j,int n)
{    
    if(j>n-1)//防止越界,最后不足k个,则j=n-1,反转剩下的所有的;否则,j值不变,还是反转k个!
        j=n-1;
    ...
}
151. 翻转字符串里的单词

给定一个字符串,逐个翻转字符串中的每个单词。
输入: “the sky is blue”,
输出: “blue is sky the”.
需要考虑s.resize()了,因为每个单词由任意个空格分隔,需要采用拼接的思想:
0.先反转字符串
1 表明前面已有字符 的标志位 bool wordcount=false;
2 while (i < n)循环中,直接在s上修改,j作为新s的末尾位置,i为原s遍历到的位置
2.1 跳过空格:while (i < n && s[i] == ’ ‘) i++;
2.2 如果 i >= n说明已经遍历结束:if (i >= n) break
2.3 否则判断wordcount, 后接空格: if(wordcount) s[j++]=’ ‘;
2.4 记录每个单词开始位置:l=j;
2.5 继续遍历,不是空格则接在j上:while ( i< n && s[i] != ’ ‘) s[j++]=s[i++];
2.6 反转本单词:reverse(s,l,j-1);
2.7 更新标志位,表明前面已有字符:wordcount=true;
3.重置s大小s.resize(j);

void reverseWords(string &s) {
    int n=s.size();
    reverse(s, 0, n - 1);
    int i = 0, j = 0, l=0;
    bool wordcount=false;//前面已有字符的标志位
    while (i<n)//直接在s上修改,j作为新s的位置,i为原s的位置
    {
        while (i<n && s[i] == ' ') i++; // 1.跳过空格
        if (i >= n) // 1.1 i >= n说明已经遍历结束
            break;
        if(wordcount) s[j++]=' '; // 1.2否则后接空格

        l=j; //2.记录每个单词开始位置
        while (i<n&&s[i] != ' ')//2.1 不是空格则接在j上
            s[j++]=s[i++];
        reverse(s,l,j-1);// 2.2 反转本单词

        wordcount=true; //3. 更新标志位
    }
    s.resize(j);//4.重置s大小
}
//从i位置到j位置反转字符串
void reverse(string &s, int i, int j)
{
    if(j<=i)
        return;
    while(i<j)
    {
        swap(s[i],s[j]);
        ++i;--j;
    }   
}
557. 反转字符串中的单词 III

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
输入: “Let’s take LeetCode contest”
输出: “s’teL ekat edoCteeL tsetnoc”
注意:在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。
这种就不需要考虑s.resize()了,因为每个单词由单个空格分隔,也就不需要采用拼接的思想了

1.跳过空格,i >= n说明已经遍历结束
2 记录每个单词开始位置 不是空格则i++,直到找到下一个空格或者 i==n 位置退出循环
2.1 反转本单词:reverse(s,j,i-1);

while (i<n)//遍历
{
    while (i<n && s[i] == ' ') i++; // 1.跳过空格
    if (i >= n) // 1.1 i >= n说明已经遍历结束
        break;

    j=i; //2.记录每个单词开始位置
    while (i<n&&s[i] != ' ')//2.1 不是空格则i++,直到找到下一个空格或者 i==n 位置退出循环
        i++;
    reverse(s,j,i-1);// 2.2 反转本单词

}

二.回文串

5.最长回文子串

转载
转载
简单描述算法如下:
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
1.对于mx > i 的情况
1.1.当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j],见下图。
1.2.当 P[j] >= mx - i 的时候,以S[j]为中心的回文子串不一定完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。至于mx之后的部分是否对称,就只能老老实实去匹配了。
2.对于 mx <= i 的情况,无法对 P[i]做更多的假设,只能P[i] = 1,然后再去匹配了。

string longestPalindrome(string s) {
    //1.为了避免更新P的时候导致越界,我们在字符串T的前增加一个特殊字符,比如说‘$’,所以算法中字符串是从1开始的。
    string t ="$#";
    for (int i = 0; i < s.size(); ++i) {//2.构造t,t中字符个数为奇数个,在加上$就是偶数个了,但是$不算
        t += s[i];
        t += '#';
    }
    //需要更新的值:mx为之前计算中最长回文子串的右端点的最大值,取得这个最大值的位置为id
    int p[t.size()] = {0}, id = 0, mx = 0, resId = 0, resMx = 0;//p为len[],
    for (int i = 0; i < t.size(); ++i) {//计算p[i]的值
        p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;//1.根据i的位置(mx > i ?) 计算p[i]的值;2 * id - i为对称的j的位置
        while (t[i + p[i]] == t[i - p[i]]) ++p[i];//2.对于选择 mx - i 和1的情况,只能老老实实再去匹配
        if (mx < i + p[i]) {//更新mx和id下一轮使用
            mx = i + p[i];//更新mx
            id = i;//更新id
        }

        if (resMx < p[i]) {//更新resMx和resId
            resMx = p[i];
            resId = i;
        }
    }
    return s.substr((resId - resMx) / 2, resMx - 1);
}

另外还有DP解法:
最长回文串_DP

string longestPalindrome(string s) {
    int dp[s.size()][s.size()] = {0}, left = 0, right = 0, len = 0;
    for (int i = 0; i < s.size(); ++i) {
        for (int j = 0; j < i; ++j) {
            dp[j][i] = (s[i] == s[j] && (i - j < 2 || dp[j + 1][i - 1]));
            if (dp[j][i] && len < i - j + 1) {
                len = i - j + 1;
                left = j;
                right = i;
            }
        }
        dp[i][i] = 1;
    }
    return s.substr(left, right - left + 1);
}
125.验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
注意:这里忽略空格,并且忽略大小写
法一:每次if判断一个字符是否为空格,如果当前符合回文串就i++,j–;

bool isPalindrome(string s) {
    int i=0, j=s.length()-1;
    while(i<j) {//如果不采用这种形式,若果遇到连续的不合法的字符,
    //就只能跳过一个,这里用while也是可以的,相应的if加上i<j的条件。
        if (!isalnum(s[i])) 
            i++;
        else if (!isalnum(s[j])) 
            j--;
        else 
            if (i<j && tolower(s[i++])!=tolower(s[j--]))       
                return false;
    }
    return true;
}

法二:每次while跳过所有空格,如果当前符合回文串就i++,j–;

 bool isPalindrome(string s) {
    int i=0, j=s.length()-1;
    while(i<j) {//如果不采用这种形式,若果遇到连续的不合法的字符,就只能跳过一个,这里用while也是可以的。
        while (!isalnum(s[i])) 
            i++;
        while (!isalnum(s[j])) 
            j--;
        if (i<j && tolower(s[i++])!=tolower(s[j--]))       
            return false;
    }
    return true;
}
680. 验证回文字符串 Ⅱ

给定一个非空字符串 s,最多删除一个字符。判断是否能成为回文字符串。
最多删除一个字符可以等价为最多略过一个字符,但是这里要考虑略过左边还是略过右边,分两种情况while即可:
while (i1 < j1 && s[i1] == s[j1]) {i1++; j1–;};
while (i2 < j2 && s[i2] == s[j2]) {i2++; j2–;};

 bool validPalindrome(string s) {
    for (int i = 0, j = s.size() - 1; i < j; i++, j--)
        if (s[i] != s[j]) 
        {
            int i1 = i, j1 = j - 1, i2 = i + 1, j2 = j;//i和j有一个位置不变,另外一个移动一位,视为删除
            //在新的i,j上遍历,如果最终i1 >= j1 || i2 >= j2;说明是回文串
            while (i1 < j1 && s[i1] == s[j1]) {i1++; j1--;};
            while (i2 < j2 && s[i2] == s[j2]) {i2++; j2--;};
            return i1 >= j1 || i2 >= j2;
        }
    return true;
}
647. 回文子串个数

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。
输入: “abc”
输出: 3
解释: 三个回文子串: “a”, “b”, “c”.

输入: “aaa”
输出: 6
说明: 6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”.
以每一个字符为中心计算一次,然后又分两种情况:1.bab,2.baab
s[i-j] == s[i+j] ?
s[i-1-j] == s[i+j] ?
注意:不用计算i+j+1(s[i+1-j] == s[i+j] ?)情况了,因为这样会重复

int countSubstrings(string s) {
    int res = 0, n = s.size();
    //以每一个字符为中心计算一次,然后又分两种情况:1.bab,2.baab
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; i-j >= 0 && i+j < n && s[i-j] == s[i+j]; j++)//bab
            res++; //substring s[i-j, ..., i+j]
        //baab,注意:不用计算i+j+1情况了,因为这样会重复
        for(int j = 0; i-1-j >= 0 && i+j < n && s[i-1-j] == s[i+j]; j++)
            res++; //substring s[i-1-j, ..., i+j]
    }
    return res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值