淘天笔试 淘天笔试题 0419

笔试时间:2025年04月19日

历史笔试传送门:

2023春招秋招笔试合集

2024春招秋招笔试合集

第一题

题目

给定长度为 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" 出现次数之和尽可能大。 例如,对于字符串 "xx­taoti­an­xxx",其中包含一个 "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 春招笔试合集 文章被收录于专栏

2025打怪升级记录,大厂笔试合集 C++, Java, Python等多种语言做法集合指南

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务