小红的字符串
思路
拿到这道题,先想想回文串的性质:一个回文串满足 s[i] == s[n-1-i],也就是首尾对称的字符必须相同。
那操作是什么呢?每次可以把一个字符循环右移到字母表的下一个字母('a'->'b'->'c'->...->'z'->'a')。问最少操作多少次能把字符串变成回文串。
怎么拆解这个问题?
既然回文串要求对称位置的字符相等,我们只需要关注每一对 (s[i], s[n-1-i]) 就行了。每一对是独立的——把这对变成相同字符的最小代价加起来,就是总答案。
那对于一对字符 (a, b),怎么用最少的右移操作让它们相等?
我们可以只移 a,也可以只移 b,还可以两个都移,让它们在某个中间字符碰头。比如 a 右移 x 步,b 右移 y 步,需要 (a + x) % 26 == (b + y) % 26,代价是 x + y。
仔细想一下:字母表是一个长度为 26 的环。两个字符在环上的距离有两个方向——顺时针走 d 步,或者逆时针走 26 - d 步。不管走哪个方向,我们都可以只移一边就搞定(让靠后的那个不动,靠前的追上去)。所以最优策略就是取两个方向中较短的那个。
具体做法
- 遍历前半段
i = 0, 1, ..., n/2 - 1 - 对每对
(s[i], s[n-1-i]),算出顺时针距离d = (s[n-1-i] - s[i] + 26) % 26 - 这一对的代价是
min(d, 26 - d) - 累加所有对的代价就是答案
如果字符串长度是奇数,中间那个字符不用管,它自己就是对称的。
代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string s;
cin >> s;
int n = s.size();
long long ans = 0;
for (int i = 0; i < n / 2; i++) {
int d = (s[n - 1 - i] - s[i] + 26) % 26;
ans += min(d, 26 - d);
}
cout << ans << endl;
return 0;
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next();
int n = s.length();
long ans = 0;
for (int i = 0; i < n / 2; i++) {
int d = (s.charAt(n - 1 - i) - s.charAt(i) + 26) % 26;
ans += Math.min(d, 26 - d);
}
System.out.println(ans);
}
}
s = input()
n = len(s)
ans = 0
for i in range(n // 2):
d = (ord(s[n - 1 - i]) - ord(s[i]) + 26) % 26
ans += min(d, 26 - d)
print(ans)
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', line => lines.push(line));
rl.on('close', () => {
const s = lines[0].trim();
const n = s.length;
let ans = 0;
for (let i = 0; i < Math.floor(n / 2); i++) {
const d = (s.charCodeAt(n - 1 - i) - s.charCodeAt(i) + 26) % 26;
ans += Math.min(d, 26 - d);
}
console.log(ans);
});
复杂度分析
- 时间复杂度:
,只遍历半个字符串
- 空间复杂度:
,存储输入字符串
总结
这道题的核心是把"整个字符串变回文"拆成"每一对对称位置的字符变相同",然后在字母表这个环上找最短距离。想通了环上两点最短距离取 min(d, 26-d) 这一步,代码写出来就几行的事。贪心的思想在于:每一对独立处理,局部最优即全局最优。



京公网安备 11010502036488号