淘天笔试 淘天笔试题 0419
笔试时间:2025年04月19日
历史笔试传送门:
第一题
题目
给定长度为 n 的正整数序列 a = (a₁, a₂, …, aₙ),小莱希望将序列恰好划分成 k 个不相交的连续区间(段),使得每一段内都存在一个长度为 m 的子序列(不要求连续),恰好是整数 1,2,…,m 的一个排列。求在所有合法划分方案中,最大的 m。若不存在任何合法方案,输出 0。
输入描述
第一行:整数 T(1 ≤ T ≤ 100),表示测试组数。每组第一行:n k(1 ≤ k ≤ n ≤ 2×10^5)。
第二行:n 个整数 aᵢ(1 ≤ aᵢ ≤ n)。保证所有测试中 ∑n ≤ 2×10^5。
输出描述
对每组测试,输出一行: 如果无任何合法划分,输出 0; 否则输出最大的 m。
样例输入
2
11 4
1 2 3 2 1 2 1 3 1 2 3
5 1
1 2 3 4 5
样例输出
2
5
解释:对于 11 4:可划分为 [1..3],[4..5],[6..7],[8..11] 4 段,每段都含子序列 {1,2},故 m_max=2。 对于 5 1:整段包含 {1,2,3,4,5} ,故 m_max=5。
参考题解
先统计 1…n 中每个数的出现次数,确定最大的上界 在 [1,上界] 内二分 m,对于每个 m,贪心地在原序列中扫描、收集不超过 m 的不同元素,凑齐 m 个就划分一段,若能得到 k 段则 m 可行,否则向下搜索。
C++:[此代码未进行大量数据的测试,仅供参考]
// C++ 版本
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int size, groups_required;
cin >> size >> groups_required;
vector<int> data(size);
for (int i = 0; i < size; i++) {
cin >> data[i];
}
// 统计每个 1..size 出现的频次
vector<int> freq(size + 2, 0);
for (int v : data) {
if (v >= 1 && v <= size)
freq[v]++;
}
// 找到最大的 max_prefix,使得对所有 i ≤ max_prefix,freq[i] ≥ groups_required
int max_prefix = 0;
for (int i = 1; i <= size; i++) {
if (freq[i] < groups_required)
break;
max_prefix = i;
}
int left = 1, right = max_prefix, result = 0;
vector<int> last_seen(size + 2, 0);
int timestamp = 1;
while (left <= right) {
int mid = (left + right) / 2;
int group_count = 0;
int current_count = 0;
// 贪心地从 data 中摘取 ≤ mid 的不同元素
for (int v : data) {
if (v <= mid && last_seen[v] != timestamp) {
last_seen[v] = timestamp;
current_count++;
if (current_count == mid) {
group_count++;
timestamp++;
current_count = 0;
if (group_count == groups_required)
break;
}
}
}
if (group_count >= groups_required) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
cout << result << "\n";
}
return 0;
}
Java:[此代码未进行大量数据的测试,仅供参考]
// Java 版本
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
int T = Integer.parseInt(in.readLine().trim());
while (T-- > 0) {
StringTokenizer st = new StringTokenizer(in.readLine());
int size = Integer.parseInt(st.nextToken());
int groupsRequired = Integer.parseInt(st.nextToken());
st = new StringTokenizer(in.readLine());
int[] data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = Integer.parseInt(st.nextToken());
}
// 统计每个 1..size 出现的频次
int[] freq = new int[size + 2];
for (int v : data) {
if (v >= 1 && v <= size) {
freq[v]++;
}
}
// 找到最大的 maxPrefix
int maxPrefix = 0;
for (int i = 1; i <= size; i++) {
if (freq[i] < groupsRequired) break;
maxPrefix = i;
}
int left = 1, right = maxPrefix, result = 0;
int[] lastSeen = new int[size + 2];
int timestamp = 1;
while (left <= right) {
int mid = (left + right) / 2;
int groupCount = 0;
int currentCount = 0;
// 贪心地从 data 中摘取 ≤ mid 的不同元素
for (int v : data) {
if (v <= mid && lastSeen[v] != timestamp) {
lastSeen[v] = timestamp;
currentCount++;
if (currentCount == mid) {
groupCount++;
timestamp++;
currentCount = 0;
if (groupCount == groupsRequired) break;
}
}
}
if (groupCount >= groupsRequired) {
result = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
out.println(result);
}
out.flush();
}
}
Python:[此代码未进行大量数据的测试,仅供参考]
import sys def main(): input = sys.stdin.readline test_cases = int(input()) for _ in range(test_cases): size, groups_required = map(int, input().split()) data = list(map(int, input().split())) # 统计每个 1..size 出现的频次 freq = [0] * (size + 2) for value in data: if 1 <= value <= size: freq[value] += 1 # 找到最大的 max_prefix,使得对所有 i ≤ max_prefix,freq[i] ≥ groups_required max_prefix = 0 for i in range(1, size + 1): if freq[i] < groups_required: break max_prefix = i # 在 [1..max_prefix] 区间内二分查找最优的 m left, right = 1, max_prefix result = 0 last_seen = [0] * (size + 2) timestamp = 1 while left <= right: mid = (left + right) // 2 group_count = 0 # 已完成的组数 current_count = 0 # 当前组已收集的不同元素数 # 贪心地从 data 中摘取 ≤ mid 的不同元素,集齐 mid 个就成一组 for value in data: if value <= mid and last_seen[value] != timestamp: last_seen[value] = timestamp current_count += 1 if current_count == mid: group_count += 1 timestamp += 1 current_count = 0 if group_count == groups_required: break if group_count >= groups_required: result = mid left = mid + 1 else: right = mid - 1 sys.stdout.write(str(result) + "\n") if __name__ == "__main__": main()
第二题
题目
给定一个仅由小写字母和 ? 字符组成的字符串 s。小红希望将字符串中的每个 ? 字符替换成某个小写字母,使得在最终替换后的字符串中,子串 "tao" 出现次数与子串 "tian" 出现次数之和尽可能大。 例如,对于字符串 "xxtaotianxxx",其中包含一个 "tao" 和一个 "tian",总计出现次数 1+1=2。
输入描述
输入一个由小写字母和 ? 组成的字符串 s,满足 1 ≤ |s| ≤ 2×10⁵。
输出描述
输出替换后的字符串(将所有 ? 替换为小写字母后的结果)。如果存在多种最优替换方案,输出任意一种即可,系统会自动校验。
样例输入
ta???an
样例输出
taotian
参考题解
1.模式匹配预处理将目标子串 “tao” 和 “tian” 插入到一个 Aho–Corasick 字典树中,标记每个终结节点的匹配贡献(out[u] 表示到达状态 u 时能额外得到多少次匹配)。 构建失败指针 fail[u],并将失败指针指向的状态上的 out 累加到当前状态,确保在自动机转移时能正确统计所有重叠匹配。2.状态转移表对于字母表中每个字母,预先计算从任意状态 u 读入该字母后应跳转到哪个状态 go[u][c],这样在 DP 时可以 O(1) 获得下一状态。3.动态规划定义 dp[i][u] 为处理到原串第 i 个字符(0≤i<n)后,自动机处于状态 u 时能获得的最大匹配数。为了节省空间,只保留一维数组 dp[u],每次滚动更新。 当当前字符是固定字母时,只沿一个字母分支转移;如果是 ?,则枚举 26 个小写字母,尝试所有可能的转移,选出得分最高的。 每次转移时加上 out[v],即到新状态 v 时新匹配到的次数。回溯重建在 DP 过程中记录每一步的最优前驱状态和使用的字符。最后在所有状态中选取最终得分最大的状态 end_state,从尾到头依次回溯,恢复出每个位置应该填的字母。5.时间与空间复杂度构建自动机:O(ΣL)(ΣL=所有模式长度之和,固定为常量) DP 转移:O(n · A · S),其中 n=|s|,A=26,S=自动机状态数(约为 ΣL);在最坏情况下 S≈7,故整体≈O(26n) 空间:O(n·S) 用于回溯指针,若只要输出结果,也可压缩为 O(S) 再额外保存一次决策,复合常数开销可接受。
C++:[此代码未进行大量数据的测试,仅供参考]
// C++ 版本
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = LLONG_MIN / 4;
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL);
string s;
if(!getline(cin, s)) return 0;
int n = s.size();
vector<string> patterns = {"tao", "tian"};
// 构建 Trie
vector<array<int,26>> go_trie;
vector<int> out, fail;
go_trie.push_back(array<int,26>{});
out.push_back(0);
for(auto &row: go_trie.back()) row = -1;
for(auto &pat: patterns){
int u = 0;
for(char ch: pat){
int c = ch - 'a';
if(go_trie[u][c] == -1){
go_trie[u][c] = go_trie.size();
go_trie.push_back(array<int,26>{});
out.push_back(0);
for(auto &r: go_trie.back()) r = -
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
2025打怪升级记录,大厂笔试合集 C++, Java, Python等多种语言做法集合指南

