这里写自定义目录标题
更多精彩内容
这里是带你游历编程世界的Dashcoding编程社,我是Dash/北航硕士/ICPC区域赛全国排名30+/给你呈现我们眼中的世界!
256题算法特训课,帮你斩获大厂60W年薪offer
原题
米哈游校招真题子序列
B站动画详解
问题分析
这道题目可以归类为字符串处理与子序列操作问题,特别考察对子序列的增删操作以及其在字符串中的分布规律。给定两个字符串 s
和 t
,我们需要通过删除或添加子序列 "mhy"
判断 s
是否可以转换为 t
。题目中的操作不限次数,且子序列的顺序必须是 "mhy"
,这意味着增删操作只能集中在这三个字符上,而不能改变字符串中其他部分的顺序。
在进行分析时,需要注意以下几点:
-
子序列特性:题目指定子序列是
"mhy"
,意味着这三个字符的出现顺序必须固定。因此,我们只能在这三个字符上进行删除或添加操作,而不能随意调整顺序。由此可知,只要剩余字符部分顺序不变,且"m"
,"h"
,"y"
的数量差满足特定条件,就可以通过多次增删操作达成转换。 -
问题简化:为了简化问题分析,可以将字符串
s
和t
拆分为两部分:一个是只包含"m"
,"h"
,"y"
的部分,另一个是剔除了这些字符后的部分。只要剔除后的部分完全一致,并且剩余部分"m"
,"h"
,"y"
的数量差能够通过添加或删除达到平衡,则可以通过多次操作将s
转化为t
。 -
数量关系:核心判断条件在于
"m"
,"h"
,"y"
三者之间的数量关系。如果s
中这三个字符的数量差可以通过相同的增删操作(如同时添加或同时删除)达到与t
的一致,那么就可以完成转换。 -
时间复杂度:由于字符串的长度限制在 1000 内,总共有最多 1000 次询问,算法必须在合理时间内完成。因此,拆分字符并进行简单的线性遍历和比较,可以确保每次询问在 O(n) 时间内解决。
通过上述分析,我们可以明确:首先要对字符进行分类统计和拆分,然后在满足字符顺序和数量关系的前提下进行比较,最终决定是否输出 "Yes"
或 "No"
。
思路分析
本题考察了字符串的操作与匹配问题,重点在于如何通过添加或删除子序列 "mhy"
来使得字符串 s
转化为目标字符串 t
。题目的关键点在于识别并操作字符串中的 "mhy"
子序列,这个子序列可以任意添加或删除,所以只要处理好其他字符的顺序一致性,同时关注 "mhy"
的匹配情况,就能判断是否可以完成转换。
思路是:首先将 s
和 t
中的非 "mhy"
字符分别提取出来,并判断它们是否相同。如果这部分相同,再比较 s
和 t
中 "mhy"
各字符的数量关系。若数量关系满足一定条件(即三者数量的增减保持一致),则可以通过添加或删除 "mhy"
实现转换。
算法实现
算法的核心是分类统计 "m"
, "h"
, "y"
这三个字符的出现次数,并将其他字符提取出来进行比较。步骤如下:
- 初始化两个计数数组
bin_s
和bin_t
,用于记录s
和t
中"m"
、"h"
和"y"
各自的数量。 - 遍历
s
和t
,分别统计"m"
、"h"
和"y"
的数量,其他字符放入新字符串new_s
和new_t
。 - 比较
new_s
和new_t
是否相同。如果相同,继续判断"m"
、"h"
和"y"
的数量差是否一致(即三者的增减保持一致)。 - 如果两部分都满足条件,则输出
"Yes"
;否则输出"No"
。
通过这样的处理,算法能够在每次询问中以线性时间复杂度完成判断,适用于较大规模的输入。
代码详解
标准代码程序
C++代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
array<int, 3> bin_s = {0, 0, 0}, bin_t = {0, 0, 0};
string s, t;
cin >> s >> t;
string filtered_s, filtered_t;
// 统计 s 中 'm', 'h', 'y' 的数量,过滤掉其他字符
for (char c : s) {
if (c == 'm') bin_s[0]++;
else if (c == 'h') bin_s[1]++;
else if (c == 'y') bin_s[2]++;
else filtered_s += c;
}
// 统计 t 中 'm', 'h', 'y' 的数量,过滤掉其他字符
for (char c : t) {
if (c == 'm') bin_t[0]++;
else if (c == 'h') bin_t[1]++;
else if (c == 'y') bin_t[2]++;
else filtered_t += c;
}
// 如果非 "mhy" 部分相同,进一步判断 "m", "h", "y" 数量关系
if (filtered_s == filtered_t) {
if ((bin_s[0] - bin_t[0] == bin_s[1] - bin_t[1]) &&
(bin_s[0] - bin_t[0] == bin_s[2] - bin_t[2])) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
} else {
cout << "No" << endl;
}
}
return 0;
}
Java代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt();
while (T-- > 0) {
int[] binS = new int[3];
int[] binT = new int[3];
String s = scanner.next();
String t = scanner.next();
StringBuilder filteredS = new StringBuilder();
StringBuilder filteredT = new StringBuilder();
// 统计 s 中 'm', 'h', 'y' 的数量,过滤掉其他字符
for (char c : s.toCharArray()) {
if (c == 'm') binS[0]++;
else if (c == 'h') binS[1]++;
else if (c == 'y') binS[2]++;
else filteredS.append(c);
}
// 统计 t 中 'm', 'h', 'y' 的数量,过滤掉其他字符
for (char c : t.toCharArray()) {
if (c == 'm') binT[0]++;
else if (c == 'h') binT[1]++;
else if (c == 'y') binT[2]++;
else filteredT.append(c);
}
// 如果非 "mhy" 部分相同,进一步判断 "m", "h", "y" 数量关系
if (filteredS.toString().equals(filteredT.toString())) {
if ((binS[0] - binT[0] == binS[1] - binT[1]) &&
(binS[0] - binT[0] == binS[2] - binT[2])) {
System.out.println("Yes");
} else {
System.out.println("No");
}
} else {
System.out.println("No");
}
}
scanner.close();
}
}
Python代码
def can_transform():
t = int(input())
for _ in range(t):
bin_s = [0, 0, 0]
bin_t = [0, 0, 0]
s = input()
t = input()
filtered_s = []
filtered_t = []
# 统计 s 中 'm', 'h', 'y' 的数量,过滤掉其他字符
for char in s:
if char == 'm':
bin_s[0] += 1
elif char == 'h':
bin_s[1] += 1
elif char == 'y':
bin_s[2] += 1
else:
filtered_s.append(char)
# 统计 t 中 'm', 'h', 'y' 的数量,过滤掉其他字符
for char in t:
if char == 'm':
bin_t[0] += 1
elif char == 'h':
bin_t[1] += 1
elif char == 'y':
bin_t[2] += 1
else:
filtered_t.append(char)
# 如果非 "mhy" 部分相同,进一步判断 "m", "h", "y" 数量关系
if filtered_s == filtered_t:
if bin_s[0] - bin_t[0] == bin_s[1] - bin_t[1] == bin_s[2] - bin_t[2]:
print("Yes")
else:
print("No")
else:
print("No")
can_transform()
Javascript代码
function canTransform() {
const readline = require('readline');
const input = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const data = [];
input.on('line', line => {
data.push(line);
});
input.on('close', () => {
let t = parseInt(data[0]);
for (let i = 1; i <= t * 2; i += 2) {
const s = data[i];
const t = data[i + 1];
const binS = [0, 0, 0];
const binT = [0, 0, 0];
let filteredS = '';
let filteredT = '';
// 统计 s 中 'm', 'h', 'y' 的数量,过滤掉其他字符
for (let char of s) {
if (char === 'm') binS[0]++;
else if (char === 'h') binS[1]++;
else if (char === 'y') binS[2]++;
else filteredS += char;
}
// 统计 t 中 'm', 'h', 'y' 的数量,过滤掉其他字符
for (let char of t) {
if (char === 'm') binT[0]++;
else if (char === 'h') binT[1]++;
else if (char === 'y') binT[2]++;
else filteredT += char;
}
// 如果非 "mhy" 部分相同,进一步判断 "m", "h", "y" 数量关系
if (filteredS === filteredT) {
if (binS[0] - binT[0] === binS[1] - binT[1] &&
binS[0] - binT[0] === binS[2] - binT[2]) {
console.log("Yes");
} else {
console.log("No");
}
} else {
console.log("No");
}
}
});
}
canTransform();
复杂度分析
时间复杂度
对于每一组询问,算法的时间复杂度可以分为以下几部分:
-
遍历字符串 s s s 和 t t t,统计每个字符串中
'm'
、'h'
、'y'
的数量,并过滤掉其他字符。这一步的时间复杂度是 O ( n ) O(n) O(n),其中 n n n 是字符串的长度。 -
比较过滤后的字符串 n e w _ s new\_s new_s 和 n e w _ t new\_t new_t。字符串的比较操作在最坏情况下也是 O ( n ) O(n) O(n)。
-
如果过滤后的字符串相同,还需要检查
'm'
、'h'
、'y'
的数量关系。这一步的操作时间是常数级别 O ( 1 ) O(1) O(1)。
由于每个查询的字符串长度最多为 1000 1000 1000,并且最多有 1000 1000 1000 组查询,因此整体时间复杂度为 O ( q × n ) O(q \times n) O(q×n),其中 q q q 是查询次数, n n n 是字符串的最大长度。考虑到题目中 q q q 和 n n n 的限制,算法在最坏情况下需要处理 1 , 000 , 000 1,000,000 1,000,000 个字符,这在现代计算机上是可以接受的。
最终的时间复杂度: O ( q × n ) O(q \times n) O(q×n)
空间复杂度
算法的空间复杂度主要体现在以下几方面:
-
存储统计
'm'
、'h'
、'y'
数量的数组 b i n _ s bin\_s bin_s 和 b i n _ t bin\_t bin_t,这两个数组的大小都是常数 O ( 1 ) O(1) O(1)。 -
过滤后的字符串 n e w _ s new\_s new_s 和 n e w _ t new\_t new_t 需要额外的空间,最坏情况下,它们的长度分别为 n n n,因此这部分的空间复杂度是 O ( n ) O(n) O(n)。
-
输入字符串和其他临时变量的存储空间也为 O ( n ) O(n) O(n),但这是问题所固有的需求。
由于我们为每个查询单独处理,空间不会累积。整体来看,空间复杂度为 O ( n ) O(n) O(n),其中 n n n 是字符串的最大长度。
最终的空间复杂度: O ( n ) O(n) O(n)
总结
本题的解决方案通过统计字符频率和过滤字符串进行比对,设计上采用了高效的线性扫描算法,使得每次查询都可以在线性时间内完成。总体时间复杂度为 O ( q × n ) O(q \times n) O(q×n),空间复杂度为 O ( n ) O(n) O(n)。该算法的实现不仅能够正确处理题目中的各种边界情况,还具有较高的可读性和扩展性,适合在大规模输入数据下运行。
本题的难点在于理解如何通过字符频率和顺序判断两个字符串能否通过有限操作相互转化。代码的逻辑清晰,思路明确,能够应对复杂度较高的情况,是一个优秀的字符串操作问题的解决方案。