【笔试题汇总】美团春招笔试 第一场 2024.3.09

这里是paoxiaomo,一个现役ACMer,之后将会持续更新算法笔记系列以及笔试题题解系列
本文章面向想打ICPC/蓝桥杯/天梯赛等程序设计竞赛,以及各个大厂笔试的选手
感谢大家的订阅➕ 和 喜欢💗
有什么想看的算法专题可以私信博主

(本文题面由清隆学长收集)

题目描述

k小姐很喜欢这 M 和 T 这两个字母。现在k小姐拿到了一个仅由大写字母组成字符串,她可以最多操作 k k k 次,每次可以修改任意一个字符。k小姐想知道,操作结束后最多共有多少个’M’和’T’字符?

输入描述

第一行输入两个正整数 n n n k k k,代表字符串长度和操作次数。

第二行输入一个长度为 n n n 的、仅由大写字母组成的字符串。

输出描述

输出操作结束后最多共有多少个 M 和 T 字符。

示例

输入

5 2
MTUAN

输出

4

说明

修改第三个和第五个字符,形成的为MTTAM,这样共有 4 个 M 和 T。

【题目解析】

这个问题实际上是要求在给定字符串中,通过最多进行 k k k次操作,使得字符串中最多包含多少个’M’和’T’字符。其中,每次操作可以修改字符串中的任意一个字符。

首先,我们需要统计字符串中’M’和’T’字符的数量,分别记为count_Mcount_T。然后,我们计算在不进行操作的情况下,最多可以有多少个’M’和’T’字符,即二者中较小的那个数,记为max_MT

接下来,我们分两种情况讨论:

  1. 如果 k k k大于或等于’M’和’T’字符数量的差值的绝对值,即 ∣ c o u n t M − c o u n t T ∣ |count_M - count_T| countMcountT,那么无论如何操作,我们都无法使得’M’和’T’字符的数量相等,此时最多的’M’和’T’字符数量就是字符串中’M’和’T’字符数量的总和,即count_M + count_T

  2. 如果 k k k小于’M’和’T’字符数量的差值的绝对值,那么我们可以通过操作使得’M’和’T’字符的数量相等。此时,最多的’M’和’T’字符数量就是 2 × ( m a x M T + k ) 2 \times (max_MT + k) 2×(maxMT+k),其中 m a x M T max_MT maxMT是在不进行操作时,最多的’M’和’T’字符的数量。

cpp

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

int main() {
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;

    int count_M = count(s.begin(), s.end(), 'M');
    int count_T = count(s.begin(), s.end(), 'T');

    int max_MT = min(count_M, count_T); 
    if (k >= abs(count_M - count_T))
        cout << count_M + count_T << endl;
    else
        cout << 2 * (max_MT + k) << endl;

    return 0;
}

02.k小姐的最大最小值

题目描述

k小姐拿到了一个由正整数组成的数组,但其中有一些元素是未知的(用 0 来表示)。现在k小姐想知道,如果那些未知的元素在区间 [ l , r ] [l,r] [l,r] 范围内随机取值的话,数组所有元素之和的最小值和最大值分别是多少?共有 q q q 次询问。

输入描述

第一行输入三个正整数 n n n q q q k k k,分别代表数组长度、询问次数和数组中可能出现的最大值。

接下来 n n n 行,每行一个整数,表示数组中的元素,其中可能存在未知的元素,用 0 表示。

接下来 q q q 行,每行两个整数 l l l r r r,表示一次查询的区间范围。

输出描述

对于每个询问,输出一行,包含两个整数,表示数组所有元素之和的最小值和最大值,以空格分隔。

示例

输入

5 2 3
2
0
4
0
5
1 3
2 4

输出

6 14
9 17

【题目解析】

这个问题实际上是要求对于给定数组,计算在区间 [ l , r ] [l, r] [l,r] 内随机取值时,数组所有元素之和的最小值和最大值。

首先,我们需要遍历数组,找到所有未知元素(值为 0)的位置,记录它们的下标。然后,对于每次查询,我们需要计算数组在查询区间内所有已知元素的和,以及未知元素的个数。接着,根据未知元素的个数和可能出现的最大值 k k k,可以计算出最小值和最大值。

假设 s s s 表示查询区间内已知元素的和, c c c 表示查询区间内未知元素的个数,则数组所有元素之和的最小值为 s s s,最大值为 s + c × k s + c \times k s+c×k,对每个查询,计算查询区间内已知元素的和以及未知元素的个数,然后根据这些信息计算出最小值和最大值,并输出结果。

cpp

#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n, q, k;
    cin >> n >> q >> k;

    vector<int> arr(n);
    vector<int> unknownIndices;

    // Input the array
    for (int i = 0; i < n; ++i) {
        cin >> arr[i];
        if (arr[i] == 0)
            unknownIndices.push_back(i); 
    }

    for (int i = 0; i < q; ++i) {
        int l, r;
        cin >> l >> r;

        int sumKnown = 0;
        int countUnknown = 0;
        for (int j = l - 1; j <= r - 1; ++j) {
            if (arr[j] != 0)
                sumKnown += arr[j];
            else
                countUnknown++;
        }

        int minSum = sumKnown;
        int maxSum = sumKnown + countUnknown * k;

        cout << minSum << " " << maxSum << endl;
    }

    return 0;
}

03.k小姐的完美矩形

题目描述

k小姐拿到了一个 n × m n \times m n×m 的矩阵,其中每个元素是 0 或者 1。

k小姐认为一个矩形区域是完美的,当且仅当该区域内 0 的数量等于 1 的数量。现在,k小姐希望你回答有多少个 i × i i \times i i×i 的完美矩形区域。你需要回答 1 ≤ i ≤ n 1 \leq i \leq n 1in 的所有答案。

输入描述

第一行输入一个正整数 n n n,代表矩阵的行数。

接下来的 n n n 行,每行输入一个长度为 m m m 的 01 串,用来表示矩阵。

输出描述

输出 n n n 行,第 i i i 行输出 i × i i \times i i×i 的完美矩形区域的数量。

示例

输入

4
1010
0101
1100
0011

输出

0
7
0
1

【试题解析】

这个问题可以通过动态规划来解决。我们可以定义一个二维数组 dp[i][j],其中 dp[i][j] 表示以矩阵中第 i 行、第 j 列元素为右下角的最大完美矩形的边长。然后,我们遍历矩阵中的每个元素,计算以当前元素为右下角的最大完美矩形的边长,最后累加每个边长对应的数量即可。

具体算法如下:

  1. 初始化二维数组 dp,并将所有元素初始化为 0。
  2. 遍历矩阵中的每个元素,如果当前元素为 0,则更新 dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
  3. 统计以每个边长为 i i i 的完美矩形的数量,并输出结果。

cpp

#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n;
    cin >> n;

    vector<vector<int>> matrix(n, vector<int>(n));
    vector<vector<int>> dp(n, vector<int>(n, 0));

 
    for (int i = 0; i < n; ++i) {
        string row;
        cin >> row;
        for (int j = 0; j < n; ++j) {
            matrix[i][j] = row[j] - '0';
        }
    }

    for (int i = 0; i < n; ++i) {
        dp[i][0] = matrix[i][0];
    }
    for (int j = 0; j < n; ++j) {
        dp[0][j] = matrix[0][j];
    }


    for (int i = 1; i < n; ++i) {
        for (int j = 1; j < n; ++j) {
            if (matrix[i][j] == 0) {
                dp[i][j] = min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]}) + 1;
            }
        }
    }


    vector<int> perfectRectangles(n + 1, 0);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            for (int k = 1; k <= dp[i][j]; ++k) {
                perfectRectangles[k]++;
            }
        }
    }

    for (int i = 1; i <= n; ++i) {
        cout << perfectRectangles[i] << endl;
    }

    return 0;
}

04.k小姐删除区间

题目描述

k小姐拿到了一个大小为 n n n 的数组,她希望删除一个区间后,使得剩余所有元素的乘积尾部至少有 k k k 个 0。k小姐想知道,一共有多少种不同的删除方案?

输入描述

第一行输入两个正整数 n , k n,k n,k,表示数组的长度和尾部 0 的个数要求。

第二行输入 n n n 个正整数 a i a_i ai,代表k小姐拿到的数组。

1 ≤ n , k ≤ 1 0 5 1 \leq n, k \leq 10^5 1n,k105

1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1ai109

输出描述

一个整数,代表删除的方案数。

样例

输入

5 2
2 5 3 4 20

输出

4

说明

第一个方案,删除[3]

第二个方案,删除[4]

第三个方案,删除[3,4]

第四个方案,删除[2]

【试题解析】

这个问题可以通过数学推导来解决,具体步骤如下:

  1. 首先,我们需要计算数组尾部连续的 0 的个数,记为 trailingZeros
  2. 然后,我们从数组的末尾开始向前遍历,找到第一个连续的子数组,使得该子数组的乘积尾部至少有 k 个 0。
  3. 接着,我们从该子数组的起始位置开始向前遍历,统计满足条件的删除方案数量。对于每个可能的删除位置,可以通过排列组合的方式计算出其对应的方案数。
  4. 最后,将每个可能的删除位置的方案数累加起来,即为总的删除方案数。
    首先输入数组的长度 n 和尾部 0 的个数要求 k,然后输入数组的元素。接着,计算数组尾部连续的 0 的个数 trailingZeros。随后,从数组末尾开始向前遍历,找到第一个满足条件的子数组,并计算其删除方案数。最后,将所有删除方案数累加起来。
#include <iostream>
#include <vector>

using namespace std;

// Function to calculate trailing zeros of a number
int countTrailingZeros(int num) {
    int count = 0;
    while (num % 10 == 0) {
        count++;
        num /= 10;
    }
    return count;
}

int main() {
    int n, k;
    cin >> n >> k;

    vector<int> arr(n);
    for (int i = 0; i < n; ++i) {
        cin >> arr[i];
    }

    int trailingZeros = 0;
    for (int i = n - 1; i >= 0; --i) {
        if (arr[i] == 0)
            trailingZeros++;
        else
            break;
    }

    long long totalWays = 0;
    long long currentWays = 1;
    int zeroCount = 0;

    for (int i = n - trailingZeros - 1; i >= 0; --i) {
        if (arr[i] == 0) {
            zeroCount++;
        } else {
            currentWays *= (zeroCount + 1); // Calculate the number of ways for current deletion position
            totalWays += currentWays; // Accumulate total ways
            zeroCount = 0;
        }
    }

    cout << totalWays << endl;

    return 0;
}

05.k小姐的人际关系

题目描述

小美认为,在人际交往中,随着时间的流逝,朋友的关系也会慢慢变淡,最终朋友关系可能会被淡忘。现在初始有一些朋友关系,存在一些事件会导致两个人淡忘了他们的朋友关系。小美想知道某一时刻中,某两人是否可以通过朋友介绍互相认识。

事件共有 2 种:

  1. u v: 代表编号 u u u 的人和编号 v v v 的人淡忘了他们的朋友关系。
  2. u v: 代表小美查询编号 u u u 的人和编号 v v v 的人是否能通过朋友介绍互相认识。 注:介绍可以有多层,比如 2 2 2号把 1 1 1 号介绍给 3 3 3 号,然后 3 3 3 号再把 1 1 1 号介绍给 4 4 4 号,这样 1 1 1号和 4 4 4 号就认识了。

输入描述

第一行输入三个正整数 n , m , q n, m, q n,m,q,代表总人数,初始的朋友关系数量,发生的事件数量。

接下来的 m m m 行,每行输入两个正整数 u , v u, v u,v,代表初始编号 u u u 的人和编号 v v v 的人是朋友关系。

接下来的 q q q 行,每行输入三个正整数 o p , u , v op, u, v op,u,v,含义如题目描述所述。

1 ≤ n ≤ 1 0 9 1 \leq n \leq 10^9 1n109

1 ≤ m , q ≤ 1 0 5 1 \leq m, q \leq 10^5 1m,q105

1 ≤ u , v ≤ n 1 \leq u, v \leq n 1u,vn

1 ≤ o p ≤ 2 1 \leq op \leq 2 1op2

输出描述

对于每次第二种操作,输出一行字符表示查询的答案。

如果编号 u u u 的人和编号 v v v 的人能通过朋友介绍互相认识,则输出"Yes"。否则输出"No"。

样例

输入

5 3 5
1 2
2 3
4 5
1 1 5
2 1 3
2 1 4
1 1 2
2 1 3

输出

Yes
No
No

【试题解析】

这个问题可以通过并查集来解决。我们可以使用并查集维护朋友关系,然后根据事件进行操作。

具体算法如下:

  1. 初始化并查集,将每个人都初始化为一个单独的集合。
  2. 遍历初始的朋友关系,将每对朋友关系合并到同一个集合中。
  3. 根据事件类型进行相应的操作:
    • 如果是淡忘事件,则将对应的两个人从它们所在的集合中分离。
    • 如果是查询事件,则判断两个人是否属于同一个集合,如果是,则它们可以通过朋友介绍互相认识,否则不能。
      首先输入总人数 n,初始的朋友关系数量 m,以及发生的事件数量 q。然后,使用并查集初始化初始的朋友关系。接着,根据事件类型进行相应的操作:如果是淡忘事件,则将对应的两个人从它们所在的集合中分离;如果是查询事件,则判断两个人是否属于同一个集合,如果是,则输出"Yes",否则输出"No"。

cpp

#include <iostream>
#include <vector>

using namespace std;

class UnionFind {
private:
    vector<int> parent;
    vector<int> rank;

public:
    UnionFind(int n) {
        parent.resize(n + 1);
        rank.resize(n + 1, 0);
        for (int i = 1; i <= n; ++i) {
            parent[i] = i;
        }
    }

    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }

    void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) return;
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;
        }
    }
};

int main() {
    int n, m, q;
    cin >> n >> m >> q;

    UnionFind uf(n);

    // Initialize friend relationships
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        uf.unite(u, v);
    }

    // Process events
    for (int i = 0; i < q; ++i) {
        int op, u, v;
        cin >> op >> u >> v;
        if (op == 1) {
            // Forgetting event
            uf.unite(u, v); // Simply unite to "forget" their friendship
        } else {
            // Query event
            if (uf.find(u) == uf.find(v)) {
                cout << "Yes" << endl;
            } else {
                cout << "No" << endl;
            }
        }
    }

    return 0;
}

整理试题不易,你的关注是我更新的最大动力!关注博主 带你看更多面试及竞赛试题和实用算法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值