更改字符串中字符
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