字符串类型题总结(不断补充中.......)

更改字符串中字符

1,丑陋的字符串

题目链接: https://www.nowcoder.com/questionTerminal/f004293aed0a46aba0e9c14d1f9f4f2b.

题目:
牛牛喜欢字符串,但是他讨厌丑陋的字符串。对于牛牛来说,一个字符串的丑陋值是字符串中相同连续字符对的个数。比如字符串 “ABABAABBB” 的丑陋值是 3,因为有一对 “AA” 和两对重叠的 “BB”。现在给出一个字符串,字符串中包含字符 ‘A’、‘B’ 和 ‘?’。牛牛现在可以把字符串中的问号改为 ‘A’ 或者 ‘B’。牛牛现在想让字符串的丑陋值最小,希望你能帮帮他。

输入描述:
输入包括一个字符串s,字符串长度length(1 ≤ length ≤ 50),字符串只包含’A’,‘B’,’?'三种字符。

输出描述:
输出一个整数,表示最小的丑陋值

示例1:
输入
A?A
输出
0

思路:
对于字符串开头和末尾的‘ ?’,比如 ???AAB???AB??ABAB??? ,总能将这些?替换为 A 或 B,使得不产生丑陋值(不管?为奇数个还是偶数个),对于中间的夹在 A/B 之间的 ?,将之替换为与前一个不同的字符,都替换完毕后,从第一个非 ? 的字符开始,到最后一个非 ? 的字符为止,统计丑陋值的个数。
参考了这个大大的思路:(链接在此)
https://blog.csdn.net/xiaxzhou/article/details/73368941?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

代码

#include<iostream>
#include<string>
using namespace std;
int minuglynum(string s){
    int n=s.size();
    int count=0;
    int ls=0,rs=n-1;
    while(s[ls]=='?')
    {
        ls++;
    }
    while(s[rs]=='?')
    {
        rs--;
    }
    for(int i=ls;i<rs;i++)
    {
        if(s[i]=='?')
        {
            if(s[i-1]=='A')
                s[i]='B';
            else
                s[i]='A';
        }
    }
    for(int j=ls;j<rs;j++)
    {
        if(s[j]==s[j+1])
            count++;
    }
    return count;
}
int main(){
    string s;
    cin>>s;
    cout<<minuglynum(s)<<endl;
    return 0;
}

2,黑化的牛牛

链接: 刷题链接在此.
题目:
牛牛变得黑化了,想要摧毁掉地球。但他忘记了开启地球毁灭器的密码。牛牛手里有一个字符串S,牛牛还记得从S中去掉一个字符就恰好是正确的密码,请你帮牛牛求出他最多需要尝试多少次密码。
如样例所示S = “ABA”,3个可能的密码是"BA", “AA”, “AB”.
当S = “A”, 牛牛唯一可以尝试的密码是一个空的密码,所以输出1.

输入描述:
输入包括一个字符串S,字符串长度length(1 ≤ length ≤ 50),其中都是从’A’到’Z’的大写字母。

输出描述:
输出一个整数,表示牛牛最多需要尝试的密码次数。

示例1
输入
ABA
输出
3

题目解析:
当字符串S中字符个数大于1时,我们需要统计去掉一个字符后得到的不重复的子串数目。一个简单直接的想法是,利用 set 中元素不重复的特性,来去除重复子串。

代码如下:

#include<iostream>
#include<string>
#include<set>
using namespace std;
string Substring(string s,int n) //删除第n个字符后的字符串
{
    int length=s.size();
    for(int i=n;i<length;i++)
    {
        s[i]=s[i+1];
    }
    return s;
}
int main(){
    string s,subs;
    int count;
    set<string> subset;
    cin>>s;
    int len=s.size();
    if(len==1)
        cout<<"1"<<endl;
    else{
        for(int i=0;i<len;i++)
        {
            subs=Substring(s,i);
            subset.insert(subs);
        }
        count=subset.size();
        cout<<count<<endl;
    }
    return 0;
}

另一种解法:
但是我们还要多思考一下,有没有更加简便的方法呢?一个个求子串再放到set里面也是比较啰嗦的呀!
我们多举几个例子来思考一下
对于 ABGHRT 这个字符串来说,显而易见有6种尝试方法,因为去掉每个字符后得到的子串均不同
而对于 ABBGHHRT 来说,去掉B或者H情况类似(去掉前一个与后一个B得到的子串完全一样),均产生重复子串。
因此,我们统计与前一个字符相同的字符个数,得到的总数就是会产生的重复子串个数,用总的子串个数(即字符串S长度)减去重复个数,得到牛牛需要尝试的次数。

代码:

#include<iostream>
#include<string>
using namespace std;
int main(){
    string s;
    int count;
    cin>>s;
    int n=s.size(),num=0;
    for(int i=1;i<n;i++)
    {
        if(s[i]==s[i-1])
            num++;//记录与前一个字符重复的字符个数
    }
    count=n-num;//牛牛可以尝试的密码总数
    cout<<count<<endl;
}

回文串类型

1,最短回文串

该题为我在做网易笔试题的时候遇到的,后来发现与力扣214题极其类似,区别是力扣214题要求在字符串前面添加字符,而笔试题要求在字符串末尾添加字符,该题为 hard 难度

链接: 题目链接.

题目:
给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。

示例 1:
输入: “aacecaaa”
输出: “aaacecaaa”
示例 2:
输入: “abcd”
输出: “dcbabcd”

题目解析:
果然题目越短越难哈~
该题虽然有好几种解法,但是解法核心思路是一样的,也就是寻找字符串 s 中从字符串第一个字符开始的最大的连续回文子串,然后将子串后面的字符反转之后加到字符串前面即可。
渣渣如我并没有想出新思路,只是记录一下官方解题思路罢了
官方题解中,解一解二均为简单解法
解一:
暴力法,直接用找子串的方法来做,官方说通过所有用例了,但是我去做的时候发现按这种方法并不能通过所有用例。所有大家还是要找一下更优的解法

string shortestPalindrome(string s)
{
    int n = s.size();
    string rev(s);
    reverse(rev.begin(), rev.end());
    int j = 0;
    for (int i = 0; i < n; i++) {
        if (s.substr(0, n - i) == rev.substr(i))//从最大子串开始找起,也有助于节省时间,如果s[0:n−i)==rev[i:] ,则该子串为最大回文子串
            return rev.substr(0, i) + s;//翻转后的字符串的前端非回文子串衔接上s,即为最终得到的最短回文子串
    }
    return "";
}

解法二:
双指针加递归法。其实这种方法时间复杂度与上面解法一样,均为O(n^2),但相比之下时间还是比第一种解法少了很多的,具体思路去看一下题解解释好了,感觉一般人想不出来

class Solution {
public:
    string shortestPalindrome(string s) {
        int n=s.size();
        int i=0;
        for(int j=n-1;j>=0;j--)
        {
            if(s[i]==s[j])
                i++; //最大回文子串一定在 [0-i]中,通过递归逐步缩小i的范围,具体证明参考题解
        }
        if(i==n) return s;
        string rev_s=s.substr(i,n);
        reverse(rev_s.begin(),rev_s.end());
        return rev_s+shortestPalindrome(s.substr(0,i))+s.substr(i);
    }
};

解法三:
千呼万唤始出来,终于说到了解法三,可喜可贺!
该法巧妙运用了kmp方法,学过数据结构的一定知道这种方法,本来这种方法是字符串匹配算法,时间复杂度O(m+n),其中m 和 n 分别为待匹配文本与字符串的长度,该法的核心思想是寻找最长相同前后缀,并生成部分匹配表,以便不从头开始匹配字符串。

现在回想第一种解法,我们寻找字符串s从第一个字符开始的最大回文子串,即s[0:n−i)==rev[i:]。如果把 s 与 r 链接起来,是不是可以用kmp算法的思想来找这个合成字符串的最长相同前后缀。
答案是可以~
创建新字符 new_s = s + “#” + reverse(s),并使用该字符串进行部分匹配表生成。
注意点是,其中:中间的 “#” 是必要的。
如果没有#,两个字符串可能会发生混淆,造成错误的答案。举例而言,字符串 aaaa。如果不在中间插入 “#”,新字符串会是 aaaaaaaa , 最长前缀大小会成为 7,这显然是错的。因此,中间的分隔符是必要的。
最后,返回最大回文子串后的子串的反转(长度为 n-f[n_new-1])+ 原字符串 s
(不了解kmp算法的小可爱们建议自己去查一下哦)

代码:

class Solution {
public:
    string shortestPalindrome(string s) {
        //kmp算法辅助解题,求最大公共前后缀
        int n=s.size();
        string rev(s);
        reverse(rev.begin(),rev.end());
        string new_s=s+"#"+rev;
        int new_n=new_s.size();
        vector<int> f(new_n,0);
        for(int i=1;i<new_n;i++)
        {
            int t=f[i-1];
            while(t>0&&new_s[i]!=new_s[t])
                t=f[t-1];
            if(new_s[i]==new_s[t])
                ++t;
            f[i]=t;
        }
        return rev.substr(0,n-f[new_n-1])+s;
    }
};

事实证明,这种方法快得很。学起来~

安利一个博主的文章(他也是转载的)
https://blog.csdn.net/weixin_30376509/article/details/99278107?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-4.channel_param

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值