
专栏导读
本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。
一、题目描述
给定一个仅由小写英文字母组成的字符串 s,请判断它是否可以在删除一个字符之后,变成一个回文串。
回文串的定义是:从左到右读和从右到左读完全相同。
如果可以,输出一个数组,数组由可删除字符的索引值构成;否则输出一个空数组。
二、输入描述
输入一个字符串 s,2 ≤ s.length ≤ 10⁵
三、输出描述
输出一个数组,内容为:N 种删除方法的字符索引值。如输入为:abca,则输出为:[1, 2],代表可以有两种删除方式:[aba, aca]。
1:删除索引值为1的字符,即删除字符b,则变为:aca,属于回文2:删除索引值为2的字符,即删除字符c,则变为:aba,属于回文
四、测试用例
测试用例1:
1、输入
abca
2、输出
1,2
3、说明
测试用例2:
1、输入
abc
2、输出
3、说明
五、解题思路
最直接的方法是枚举每个下标,删除字符后再判断剩余字符串是否为回文,但字符串长度最大为 10^5,这种方法时间复杂度为 O(n²),会超时。
本题需要找出“所有”能够删除的下标,不能只处理第一对不相等字符。例如字符串 aaba,删除下标 0、1、2 都可以得到回文串,因此仅检查第一处不匹配位置会漏答案。
设准备删除的下标为 i,它关于原字符串中心的镜像位置为 mirror = n - 1 - i。
当 i <= mirror 时,删除左半部分字符 s[i] 后:
- 删除位置外面的
i对字符不会发生错位,所以这些字符在原串中必须已经镜像相等; - 删除字符后,内部剩余区间
s[i+1...mirror]必须本身是回文串。
当 i > mirror 时完全对称,需要外面的 mirror 对字符已经匹配,同时内部区间 s[mirror...i-1] 是回文。
因此先从两端向中间统计连续相等的外层字符对数量 matchedOuter,即可 O(1) 判断第一个条件。
第二个条件要求频繁判断某个子串是否为回文。如果每次重新双指针扫描,最坏仍然达到 O(n²)。所以使用 Manacher 算法预处理奇数回文半径数组和偶数回文半径数组。预处理时间 O(n),之后任意连续子串是否为回文均可 O(1) 判断。
最后枚举所有 n 个删除位置,根据上述两个条件判断并加入答案。
数据结构主要使用两个整型数组保存 Manacher 的奇偶回文半径,以及一个 List 保存答案。
六、Python算法源码
def build_manacher(s):
n = len(s)
# odd[i]:以 i 为中心的最长奇数回文半径
odd = [0] * n
# even[i]:以 i-1 和 i 之间为中心的最长偶数回文半径
even = [0] * n
# --------------------
# 计算奇数长度回文半径
# --------------------
left, right = 0, -1
for i in range(n):
if i > right:
k = 1
else:
k = min(
odd[left + right - i],
right - i + 1
)
# 向左右两侧扩展
while (
i - k >= 0
and i + k < n
and s[i - k] == s[i + k]
):
k += 1
odd[i] = k
if i + k - 1 > right:
left = i - k + 1
right = i + k - 1
# --------------------
# 计算偶数长度回文半径
# --------------------
left, right = 0, -1
for i in range(n):
if i > right:
k = 0
else:
k = min(
even[left + right - i + 1],
right - i + 1
)
while (
i - k - 1 >= 0
and i + k < n
and s[i - k - 1] == s[i + k]
):
k += 1
even[i] = k
if i + k - 1 > right:
left = i - k
right = i + k - 1
return odd, even
def solve(s):
n = len(s)
# Manacher 预处理
odd, even = build_manacher(s)
def is_palindrome(left, right):
"""
O(1) 判断 s[left:right+1] 是否为回文
"""
# 空串或单字符均为回文
if left >= right:
return True
length = right - left + 1
if length % 2 == 1:
center = (left + right) // 2
return odd[center] >= length // 2 + 1
center = (left + right + 1) // 2
return even[center] >= length // 2
# 统计原字符串从两端开始连续相等的字符对数量
matched_outer = 0
while (
matched_outer < n // 2
and s[matched_outer]
== s[n - 1 - matched_outer]
):
matched_outer += 1
ans = []
# 枚举每一个删除位置
for i in range(n):
mirror = n - 1 - i
if i <= mirror:
# 删除左半部分字符:
# 外层 i 对字符必须已经匹配,
# 删除后中间部分也必须是回文。
if (
i <= matched_outer
and is_palindrome(i + 1, mirror)
):
ans.append(i)
else:
# 删除右半部分字符,对称处理
if (
mirror <= matched_outer
and is_palindrome(mirror, i - 1)
):
ans.append(i)
return ans
s = input().strip()
ans = solve(s)
print(",".join(map(str, ans)), end="")
七、JavaScript算法源码
const fs = require("fs");
const s = fs.readFileSync(0, "utf8").trim();
function buildManacher(s) {
const n = s.length;
// odd[i]:以 i 为中心的最长奇数回文半径
const odd = new Array(n).fill(0);
// even[i]:以 i-1 和 i 之间为中心的最长偶数回文半径
const even = new Array(n).fill(0);
// --------------------
// 奇数长度回文
// --------------------
let left = 0;
let right = -1;
for (let i = 0; i < n; i++) {
let k;
if (i > right) {
k = 1;
} else {
k = Math.min(
odd[left + right - i],
right - i + 1
);
}
// 向左右扩展
while (
i - k >= 0 &&
i + k < n &&
s[i - k] === s[i + k]
) {
k++;
}
odd[i] = k;
if (i + k - 1 > right) {
left = i - k + 1;
right = i + k - 1;
}
}
// --------------------
// 偶数长度回文
// --------------------
left = 0;
right = -1;
for (let i = 0; i < n; i++) {
let k;
if (i > right) {
k = 0;
} else {
k = Math.min(
even[left + right - i + 1],
right - i + 1
);
}
while (
i - k - 1 >= 0 &&
i + k < n &&
s[i - k - 1] === s[i + k]
) {
k++;
}
even[i] = k;
if (i + k - 1 > right) {
left = i - k;
right = i + k - 1;
}
}
return [odd, even];
}
function solve(s) {
const n = s.length;
const [odd, even] = buildManacher(s);
/**
* 根据 Manacher 数组,
* O(1) 判断一个连续子串是否为回文。
*/
function isPalindrome(left, right) {
// 空串和单字符都是回文
if (left >= right) {
return true;
}
const len = right - left + 1;
if (len % 2 === 1) {
const center = Math.floor((left + right) / 2);
return odd[center] >= Math.floor(len / 2) + 1;
}
const center = Math.floor((left + right + 1) / 2);
return even[center] >= len / 2;
}
/*
* 计算原字符串从最外侧开始,
* 连续能够镜像匹配多少对字符。
*/
let matchedOuter = 0;
while (
matchedOuter < Math.floor(n / 2) &&
s[matchedOuter] === s[n - 1 - matchedOuter]
) {
matchedOuter++;
}
const ans = [];
for (let i = 0; i < n; i++) {
const mirror = n - 1 - i;
if (i <= mirror) {
// 删除左半部分字符
if (
i <= matchedOuter &&
isPalindrome(i + 1, mirror)
) {
ans.push(i);
}
} else {
// 删除右半部分字符,对称判断
if (
mirror <= matchedOuter &&
isPalindrome(mirror, i - 1)
) {
ans.push(i);
}
}
}
return ans;
}
process.stdout.write(solve(s).join(","));
八、C算法源码
#include <stdio.h>
#include <string.h>
#define MAX_N 100005
static char s[MAX_N];
/* 奇数回文半径 */
static int oddRadius[MAX_N];
/* 偶数回文半径 */
static int evenRadius[MAX_N];
static int min_int(int a, int b) {
return a < b ? a : b;
}
/*
* Manacher 算法预处理。
* 总时间复杂度 O(n)。
*/
static void build_manacher(int n) {
int left = 0;
int right = -1;
/*
* oddRadius[i] 表示以 i 为中心的
* 最长奇数长度回文半径。
*/
for (int i = 0; i < n; i++) {
int k;
if (i > right) {
k = 1;
} else {
k = min_int(
oddRadius[left + right - i],
right - i + 1
);
}
/* 向两边继续扩展 */
while (
i - k >= 0 &&
i + k < n &&
s[i - k] == s[i + k]
) {
k++;
}
oddRadius[i] = k;
if (i + k - 1 > right) {
left = i - k + 1;
right = i + k - 1;
}
}
left = 0;
right = -1;
/*
* evenRadius[i] 表示以 i-1 与 i
* 之间的位置为中心的偶数回文半径。
*/
for (int i = 0; i < n; i++) {
int k;
if (i > right) {
k = 0;
} else {
k = min_int(
evenRadius[left + right - i + 1],
right - i + 1
);
}
while (
i - k - 1 >= 0 &&
i + k < n &&
s[i - k - 1] == s[i + k]
) {
k++;
}
evenRadius[i] = k;
if (i + k - 1 > right) {
left = i - k;
right = i + k - 1;
}
}
}
/*
* O(1) 判断 s[left...right] 是否为回文。
*/
static int is_palindrome(int left, int right) {
/* 空串和单个字符都为回文 */
if (left >= right) {
return 1;
}
int len = right - left + 1;
if (len & 1) {
int center = (left + right) / 2;
return oddRadius[center] >= len / 2 + 1;
} else {
int center = (left + right + 1) / 2;
return evenRadius[center] >= len / 2;
}
}
int main(void) {
scanf("%100000s", s);
int n = (int)strlen(s);
build_manacher(n);
/*
* matchedOuter 表示原字符串最外层
* 连续镜像相等的字符对数量。
*/
int matchedOuter = 0;
while (
matchedOuter < n / 2 &&
s[matchedOuter] ==
s[n - 1 - matchedOuter]
) {
matchedOuter++;
}
int first = 1;
for (int i = 0; i < n; i++) {
int mirror = n - 1 - i;
int valid = 0;
if (i <= mirror) {
/*
* 删除左半部分字符:
* 外层字符必须已匹配,
* 中间剩余区间必须为回文。
*/
valid =
i <= matchedOuter &&
is_palindrome(i + 1, mirror);
} else {
/*
* 删除右半部分字符,
* 与左半部分对称处理。
*/
valid =
mirror <= matchedOuter &&
is_palindrome(mirror, i - 1);
}
if (valid) {
if (!first) {
printf(",");
}
printf("%d", i);
first = 0;
}
}
return 0;
}
九、C++算法源码
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> oddRadius;
vector<int> evenRadius;
/*
* Manacher 算法:
* 预处理奇数和偶数回文半径。
*/
void buildManacher(const string& s) {
int n = (int)s.size();
oddRadius.assign(n, 0);
evenRadius.assign(n, 0);
// --------------------
// 奇数长度回文
// --------------------
for (int i = 0, left = 0, right = -1;
i < n;
i++) {
int k;
if (i > right) {
k = 1;
} else {
k = min(
oddRadius[left + right - i],
right - i + 1
);
}
// 向左右继续扩展
while (
i - k >= 0 &&
i + k < n &&
s[i - k] == s[i + k]
) {
++k;
}
oddRadius[i] = k;
if (i + k - 1 > right) {
left = i - k + 1;
right = i + k - 1;
}
}
// --------------------
// 偶数长度回文
// --------------------
for (int i = 0, left = 0, right = -1;
i < n;
i++) {
int k;
if (i > right) {
k = 0;
} else {
k = min(
evenRadius[left + right - i + 1],
right - i + 1
);
}
while (
i - k - 1 >= 0 &&
i + k < n &&
s[i - k - 1] == s[i + k]
) {
++k;
}
evenRadius[i] = k;
if (i + k - 1 > right) {
left = i - k;
right = i + k - 1;
}
}
}
/*
* 使用 Manacher 预处理结果,
* O(1) 判断任意连续子串是否为回文。
*/
bool isPalindrome(int left, int right) {
// 空串和单字符都是回文
if (left >= right) {
return true;
}
int len = right - left + 1;
if (len & 1) {
int center = (left + right) / 2;
return oddRadius[center]
>= len / 2 + 1;
} else {
int center = (left + right + 1) / 2;
return evenRadius[center]
>= len / 2;
}
}
vector<int> solve(const string& s) {
int n = (int)s.size();
buildManacher(s);
/*
* 从字符串两端开始,
* 统计连续镜像相等的字符对数量。
*/
int matchedOuter = 0;
while (
matchedOuter < n / 2 &&
s[matchedOuter] ==
s[n - 1 - matchedOuter]
) {
++matchedOuter;
}
vector<int> ans;
// 枚举所有删除位置
for (int i = 0; i < n; i++) {
int mirror = n - 1 - i;
if (i <= mirror) {
/*
* i 位于左半部分。
* 外层 i 对字符必须原本相等;
* 删除后中间区间 [i+1, mirror]
* 必须为回文。
*/
if (
i <= matchedOuter &&
isPalindrome(i + 1, mirror)
) {
ans.push_back(i);
}
} else {
/*
* i 位于右半部分,
* 与左侧情况完全对称。
*/
if (
mirror <= matchedOuter &&
isPalindrome(mirror, i - 1)
) {
ans.push_back(i);
}
}
}
return ans;
}
int main() {
string s;
cin >> s;
vector<int> ans = solve(s);
for (int i = 0; i < (int)ans.size(); i++) {
if (i > 0) {
cout << ",";
}
cout << ans[i];
}
return 0;
}
🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 新系统 200分)
🏆本文收录于,华为OD机试真题(Python/JS/C/C++)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

10万+

被折叠的 条评论
为什么被折叠?



