《字典序最小回文构造问题》
题面
问题理解
我们需要将一个给定的字符串转换为回文字符串,并且要求字典序尽可能小。在这个过程中,最多可以更改字符串中的两个字符。每个字符可以被更改为任意的小写字母。
解题思路
- 回文特性:回文字符串的特点是正读和反读都相同。因此,字符串的前半部分和后半部分应该是对称的。
- 字典序最小:为了使字典序最小,我们需要尽可能将字符改为'a'。
- 最多更改两个字符:我们需要在最多更改两个字符的前提下,使得字符串成为回文且字典序最小。
算法步骤
-
找到不对称的位置:遍历字符串的前半部分,找到所有不对称的位置(即
s[i] != s[n-i-1])。 -
处理不对称位置:
- 如果不对称位置的数量为2,直接将这两个位置的字符改为它们中的较小值。
- 如果不对称位置的数量为1,需要考虑字符串的长度是否为奇数。如果是奇数,可以考虑将中间字符也改为'a'。
- 如果不对称位置的数量为0,说明字符串已经是回文,但可能字典序不是最小。此时,我们可以尝试将前半部分中的某个字符改为'a',前提是该字符不是'a'。
具体实现
std::string solution(std::string s) {
// write code here
int n = s.size();
vector<int> notSamePos;
for(int i=0;i<n/2;i++){
if(s[i] != s[n-i-1]){
notSamePos.push_back(i);
}
}
if(notSamePos.size() == 2){
s[notSamePos[0]] = s[n-notSamePos[0]-1] = min(s[notSamePos[0]], s[n-notSamePos[0]-1]);
s[notSamePos[1]] = s[n-notSamePos[1]-1] = min(s[notSamePos[1]], s[n-notSamePos[1]-1]);
} else if(notSamePos.size() == 1){
if(n&1){
if(min(s[notSamePos[0]], s[n-notSamePos[0]-1]) == 'a'){
s[notSamePos[0]] = s[n-notSamePos[0]-1] = min(s[notSamePos[0]], s[n-notSamePos[0]-1]);
s[n/2] = 'a';
} else {
s[notSamePos[0]] = s[n-notSamePos[0]-1] = 'a';
}
} else {
s[notSamePos[0]] = s[n-notSamePos[0]-1] = 'a';
}
} else {
for(int i=0;i<(n+1)/2;i++){
if(s[i] == 'a'){
continue;
}
s[i] = s[n-i-1] = 'a';
break;
}
}
return s;
}
《字符串首尾相同子序列计数》
题面
问题理解
我们需要计算一个字符串中,有多少个子序列的首尾字符相同。子序列是指从原字符串中按原顺序取出若干字符(可以不连续)组成的新字符串。
数据结构选择
利用一个二维向量 pos,其中 pos[i] 存储了字符 'a' + i 在字符串中出现的所有位置。这个数据结构有助于我们快速查找某个字符的所有出现位置。
算法步骤
-
初始化:
- 创建一个二维向量
pos,用于存储每个字符出现的位置。 - 初始化
ret为字符串的长度n,因为每个字符本身就是一个子序列。
- 创建一个二维向量
-
遍历字符串:
- 对于每个字符
s[i],查找之前所有出现过的相同字符的位置j。 - 对于每个位置
j,计算以s[i]为结尾、以s[j]为开头的子序列数量。这个数量可以通过fastPow(2, i - j - 1)计算,因为在这两个位置之间的字符可以选择或不选择。 - 将计算结果累加到
ret中,并对998244353取模。
- 对于每个字符
-
更新位置:
- 将当前字符的位置
i添加到pos[s[i] - 'a']中。
- 将当前字符的位置
具体实现
int mod = 998244353;
// 快速幂
long long fastPow(long long base, long long exp) {
long long result = 1;
while (exp > 0) {
if (exp % 2 == 1) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exp /= 2;
}
return result;
}
int solution(std::string s) {
// write code here
int n = s.size();
int ret=n;
vector<vector<int>> pos(26);
for(int i=0;i<n;i++){
for(int j : pos[s[i] - 'a']){
ret = (ret + fastPow(2,i-j-1)) % mod;
}
pos[s[i] - 'a'].push_back(i);
}
return ret;
}
《糖果均匀分配问题》
题面
问题理解
题目要求我们将两种糖果分配给小朋友,使得每个小朋友至少得到一种糖果,并且每种糖果至少分给一个小朋友。目标是使得分到糖果最少的小朋友得到的糖果数尽可能多。
算法步骤
-
初始化结果变量:我们用
ret来记录当前找到的最大最小糖果数。 -
遍历可能的分配方案:
- 我们用
i表示分配第一种糖果的小朋友数量,n-i表示分配第二种糖果的小朋友数量。 - 遍历
i由 1 到n-1,因为每种糖果至少要分给一个小朋友。
- 我们用
-
检查分配是否可行:
- 如果
i大于第一种糖果的数量a,或者n-i大于第二种糖果的数量b,则当前分配方案不可行,跳过。
- 如果
-
计算当前分配方案的最小糖果数:
- 计算第一种糖果每个小朋友能分到的数量
a / i。 - 计算第二种糖果每个小朋友能分到的数量
b / (n-i)。 - 取两者的最小值作为当前分配方案的最小糖果数。
- 计算第一种糖果每个小朋友能分到的数量
-
更新结果:
- 如果当前分配方案的最小糖果数大于
ret,则更新ret。
- 如果当前分配方案的最小糖果数大于
-
返回结果:
- 最终返回
ret,即在最优分配方案下,得到糖果最少的小朋友能得到的最大糖果数。
- 最终返回
具体实现
int solution(int n, int a, int b) {
// write code here
int ret = 0;
for(int i=1;i<=n-1;i++){
if(n - i > b){
continue;
}
if(i > a){
break;
}
ret = max(ret, min(a / i, b / (n - i)));
}
return ret;
}