Bootstrap

美团2024年春招第一场笔试[测开方向],编程题+选择题详解,ACM式C++解法

编程题&选择题

编程题

小美的平衡矩阵

小美拿到一个n * n 的矩阵,其中每个元素是0或者1,小美认为0的个数恰好等于1的个数时矩阵就是完美的,现在,小美希望你回答有多少个i * i 的完美矩形区域。你需要回答 1≤ i ≤ n 的所有答案。

思路

涉及到的知识点是前缀和,说实话看见矩阵就想到前缀和了,但是忘记怎么写了o(╥﹏╥)o

复习了一下我胡汉三又回来了,复习时写了超级详细的笔记前缀和大总结!!

okok,回到这道题
仔细阅读题目可以得到的信息:

  • 问有多少个i * i的矩阵,说明矩阵必须是正方形,那么边长为奇数的不可能成为平衡矩阵
  • 每一个矩阵里面都是0或者1,如果是平衡矩阵的话,矩阵和 == 矩阵元素数量 / 2

很显然的前缀和思路了,得到任意前缀和的1个数并不难,难在如何统计所有i * i的矩阵
嵌套for循环就可以
首先,左上角起始节点的(i,j)的范围因为右边还有留下i的长度,所以左上角起始节点:
假设边长为len

for(int i = 1; i <= n - len + 1; i ++)
	for(int j = 1; j <= n - len + 1; j ++)

留下了至少一个len边长矩阵的空间
以起始节点为基础,右下角节点:

l = i + len - 1
r = j + len - 1

所以现在左上角节点(i,j),右下角节点(l,r)
判断平衡矩阵加加的条件是

if(sum[l][r] - sum[i - 1][j] - sump[i][j - 1] + sum[i - 1][j - 1] == (len * len) / 2)

200 OK

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 210;
int s[N][N];
int a[N][N];

int main(){
    int n;
    cin >> n;  // 必须首先读取 n
    char c;

    // 接收输入数组
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= n; j ++){
            cin >> c;  // 先用 c 接收,再将其转为整数类型
            a[i][j] = c - '0'; 
        }
    }

    // 构建前缀和数组
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= n; j ++){
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
    }

    // 遍历每一个长度为 len 的矩阵
    for(int len = 1; len <= n; len ++){
        int count = 0;  // 计数应该在这里初始化,每次len变化便重新计数
        if(len % 2 == 0){  // 只考虑偶数长度
            for(int i = 1; i <= n - len + 1; i ++){
                for(int j = 1; j <= n - len + 1; j ++){
                    int l = i + len - 1;
                    int r = j + len - 1;
                    if((s[l][r] - s[i - 1][r] - s[l][j - 1] + s[i - 1][j - 1]) * 2 == len * len) count ++;
                }
            }
            cout << count << endl;
        }
        else{
            cout << 0 << endl;
        }
    }
}

小美的数组询问

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

思路

不难,一点也不难
首先题目的意思是,将a[i] == 0的元素替换掉,替换范围是[l,r],在这个区间内替换处最小值和最大值,最后将替换后的a[]数组输出
只需要记录0的个数,最后个数*最大值/最小值输出即可

但是要注意数据范围
在这里插入图片描述
a[i]最大可以达到109,n最大是105,所以和sum完全可以超过int类型,应该用long long,在存和

代码

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N];
typedef long long ll ;
int n, m;
int main() {
    cin >> n >> m;
    ll sum = 0;
    int cnt = 0;
    for(int i = 0; i < n; i ++){
        cin >> a[i];
        sum += a[i];
        if(a[i] == 0){
            cnt ++;
        }
    }
    while(m --){
        int l, r;
        cin >> l >> r;
        printf("%lld %lld\n",cnt * l + sum, cnt * r + sum);
    }
}

验证工号

假设美团的工号是由18位数字组成的,由以下规则组成:

  • 前面6位代表是哪个部门
  • 7-14位代表是出生日期,范围是1900.01.01-2023.12.31
  • 15-17位代表是哪个组,不能是完全一样的3位数字
  • 18位是一位的校验和,假设是 x ,则需要满足(𝑥+𝑎1+ 𝑎2+𝑎3+a4+…+a17)mod 8 = 1,a1 - a17代表了前面17位数字

现在需要写一份代码,判断输入的工号是否符合对应的规则。
提示:出生日期这里需要判断闰年。闰年判断的条件是能被 4 整除, 但不能被 100 整除;或能被 400 整除。

思路

需要非常细心
其中值得注意的是,判断部门号是否合法,只要有一个部门号为true就行,所以这里定义了两个bool变量
具体代码实现为:

bool validDepartments = false;//最终判断是否合法的变量
        for(int i = 0; i < n; i ++){
            bool flag = true;//临时bool变量
            for(int j = 0; j < 6; j ++){
                if(departments[i][j] != id[j]){
                    flag = false;//如果不符合当前的部门号,就break出当前的部门号,换下一个部门号
                    break;
                }
            }
            //只要有一个合法的部门号,就将最终bool变量赋值为true,并跳出for部门号的循环
            if(flag){
                validDepartments = true;
                break;
            }
        }

代码

#include <iostream>
using namespace std;
/*
 - 前面6位代表是哪个部门
 - 7-14位代表是出生日期,范围是1900.01.01-2023.12.31
 - 15-17位代表是哪个组,不能是完全一样的3位数字
 - 18位是一位的校验和,假设是 x ,则需要满足(𝑥+𝑎^1^+ 𝑎^2^+𝑎^3^+a^4^+...+a^17^)mod 8 = 1,a^1^ - a^17^代表了前面17位数字
*/
//判断闰年
bool loopYear(int year){
    return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
//判断日期是否合理
bool isVaildDate(int year, int month, int data){
    if(year < 1900 || year >2023 || month < 1 || month > 12 || data < 1){
        return false;
    }
    int dataValid[12] = {31, loopYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    return data <= dataValid[month - 1];
}
//三位数是否相同
bool isSame(int a, int b, int c){
    return a == b && b == c;
}
const int N = 100;
int main() {
    int n;
    cin >> n;
    int departments[n][6];
    //接收合法的部门号departments
    for(int i = 0; i < n; i ++){
        for(int j = 0; j < 6; j ++){
            char c;
            cin >> c;
            departments[i][j] = c - '0';
        }
    }
    int q;
    int id[18];
    cin >> q;
    while(q --){
        //接收员工号id[]
        for(int i = 0; i < 18; i ++){
            char c;
            cin >> c;
            id[i] = c - '0';
        }
        //判断前6为部门号是否正确
        bool validDepartments = false;
        for(int i = 0; i < n; i ++){
            bool flag = true;
            for(int j = 0; j < 6; j ++){
                if(departments[i][j] != id[j]){
                    flag = false;
                    break;
                }
            }
            //只要有一个合法就是true
            if(flag){
                validDepartments = true;
                break;
            }
        }
        if(!validDepartments){
            cout << "error" << endl;
            continue;
        }
        //判断生日日期是否合法
        int year = id[6] * 1000 + id[7] * 100 + id[8] * 10 + id[9];
        int month = id[10] * 10 + id[11];
        int data = id[12] * 10 + id[13];
        if(!isVaildDate(year, month, data)){
            cout << "error" << endl;
            continue;
        }
        //检查后3位是否相同
        if(isSame(id[14], id[15], id[16])){
            cout << "error" << endl;
            continue;
        }
        //校验和
        int checkSum = id[17];
        int sum = 0;
        for(int i = 0; i < 17; i ++) sum += id[i];
        if((sum + checkSum) % 8 != 1){
            cout << "error" << endl;
        }else{
            cout << "ok" << endl;
        }
    }
}
// 64 位输出请用 printf("%lld")

选择题

1.在计算机网络中,端口号的作用是什么

A 区分主机内不同进程

2.HTTPS协议通过使用哪些机制来确保通信的安全性

A 加密和身份验证

3.Etag用于标识资源的唯一标识符,他可以用于

A 验证资源发生变化
Etag有摘要算法,这个就是用来确定资源是否发生变化的

4.在一个单道系统中,有4个作业P、Q、R和S,执行时间分别为2小时、4小时、6小时和8小时,P和Q同时在0时到达,R和S在2小时到达,采用短作业优先算法时,平均周转时间为

D 9小时
1 作业到达时间和执行时间:
作业P: 执行时间 2小时,0时到达
作业Q: 执行时间 4小时,0时到达
作业R: 执行时间 6小时,2时到达
作业S: 执行时间 8小时,2时到达

2 作业执行顺序
由短作业优先顺序可知,时间0时,P和Q都到达,P将先执行(执行时间短)
P从0执行到2
在时间2,P执行完,此时R和S也到达了,接下来选择Q执行
Q从2执行到6
在时间6,剩下R和S,选择R执行,从6到12
在时间12,执行最后一个S,从12到20

3 计算周转时间
周转时间定义为作业完成时刻与到达时刻之差。
P的周转时间 = 完成时间2 - 到达时间0 = 2小时
Q的周转时间 = 完成时间6 - 到达时间0 = 6小时
R的周转时间 = 完成时间12 - 到达时间2 = 10小时
S的周转时间 = 完成时间20 - 到达时间2 = 18小时

平均周转时间:(2 + 6 + 10 + 18) / 4 = 36 / 4 = 9小时

5.系统中现有一个任务进程在11:30到达系统,如果在14:30开始运行这个任务进程,其运行时间为3小时,现求这个任务进程的响应比为

B 2
响应比计算公式:
在这里插入图片描述
任务从11:30到达,14:30开始执行,等待了3h,执行3h,所以(3 + 3) / 3 = 2

6.在一个物流管理系统中,需要一个功能来处理不同类型的货物运输请求,如陆运、空运或海运。该系统应能够根据运输类型的不同选择不同的处理策略。哪种设计模式最合适

c 策略模式

策略模式允许在运行时选择算法或行为的模式。它定义了一系列的算法,并将每一个算法封装起来,使它们可以互换使用。策略模式让算法的变化独立于使用算法的客户。

这种模式特别适用于:
系统有多种类似的行为或算法,但是在运行时需要动态地决定使用哪一个。
需要轻松地切换算法或行为。
需要避免多重条件选择语句(如多重if-else或switch-case)。

7.对关键码序列{9, 27, 18, 36, 45, 54, 63}进行堆排序,输出2个最大关键码后的剩余堆是

D 大根堆

8.以下哪个设计模式主要用于在不改变原始类的情况下扩展其功能

A 装饰器模式
装饰器模式允许动态地添加或修改对象的功能而不修改其原有的类结构。这种模式创建了一个装饰类,用来包装原有的类,提供额外的功能,而原始类的使用者无需改变。
装饰器模式的特点
动态扩展:在运行时增加对象的新功能。
增加功能:为对象添加额外的功能,而不改变其结构。
灵活性:可以选择添加或不添加新功能,甚至可以组合多个装饰来增强对象的功能。
不修改原有类:不需要修改原始类的代码或继承机制。

9.设哈希表长m=10,有一堆数据元素,关键字分别为{14, 25, 36, 47, 58, 69, 80},按照哈希函数为H(key)=key%10,如用线性探测法处理冲突,求关键字90填装的哈希表位置的序号是

c 1
不多解释

10.在一颗深度为8的完全二叉树中,最少可以有多少个结点,最多可以有多少个结点?

A 128和255
最少也就是最后一层只有一个节点,前7层满了:2^7 - 1 = 127,最后一层一个节点:127 + 1 = 128
最多也就是最后一层也是满的,前8层均满了:2^8 - 1 = 155

11.在编译器的目标代码生成阶段,以下哪个不是优化的主要目标是

D 减少程序编译时间
编译器生成目标代码阶段主要目标包括:提高执行速度,减少内存占用,减少代码大小,降低能耗
一般,增加编译速度,增强程序功能,改进用户界面等都不是优化目标

12.若入栈序列为1, 3, 5, 2, 4, 6,且进栈和出栈可以穿插进行,则不可能的输出序列为

B 不多说

13.在用KMP算法进行模式匹配时,若是指向模式串"mnopmn"的指针在指到第5个字符"m"时发生失配,则指针回溯的位置为()。

注:字符串中字符从字符数据1号位开始存储,也即从1开始编号。
C 1
对于给定的模式串 “mnopmn”,我们首先需要计算这个字符串的部分匹配表。
1 匹配表
模式串: “mnopmn”
对于 ‘m’ (位置0),没有前缀和后缀,因此表的值为0。
对于 ‘mn’ (位置1),同样没有匹配的前缀和后缀,因此表的值为0。
对于 ‘mno’ (位置2),也是0。
对于 ‘mnop’ (位置3),也是0。
对于 ‘mnopm’ (位置4),前缀 [“m”] 和后缀 [“m”] 匹配,最长公共元素长度为1。
对于 ‘mnopmn’ (整个字符串,位置5),前缀 [“mn”] 和后缀 [“mn”] 匹配,最长公共元素长度为2。
因此,部分匹配表为 [0, 0, 0, 0, 1, 2]。
2 确定回溯位置
当指针在模式串的第5个字符 ‘m’ 失配时,我们查看部分匹配表的第4个位置的值,即1。
这意味着在失配时,模式串的指针应该回溯到位置1。换句话说,‘m’ 失配后,我们应该重新开始匹配模式串中的第2个字符(即位置1的字符,因为索引从0开始)。

14.代码需要经过一系列步骤编译成机器指令,根据完成任务不同,可以将编译器的组成部分划分为前端与后端。下列选项是编译器前端在编译源程序时编译的顺序,正确的是

A 词法分析器->语法分析器->中间代码生成器

正确顺序:词法分析->句法分析->语义分析->中间代码生成

15.不同的数据存放区存放的数据和对应的管理方法是不同的。对于某些数据,如果在编译期间就可以确定数据对象的大小和数据对象的数目,在编译期间为数据对象分配存储空间,这些数据对应的存储分配策略是

D 静态存储分配
数据区可以分为静态数据区和动态数据区,动态数据区可以分为堆区和栈区。之所以这样划分,是因为不同的数据存放区存放的数据和对应的管理方法是不同的。静态存储区、堆区和栈区的存储空间分配对应三种不同的策略,分别是静态存储分配、堆式存储分配以及栈式存储分配。
静态数据区对应的静态存储分配在编译期间为数据对象分配存储空间,这要求编译期间就可以确定数据对象的大小和数据对象的数目。
静态数据区(Static Data Area)
定义和特点:
静态数据区,也称为全局/静态存储区,主要用于存储静态分配的变量,包括全局变量、静态变量以及常量(如字符串常量)。
这些数据的内存分配在编译时就已确定,并在程序的整个生命周期内持续存在。
这个区域的内存不需要程序员手动管理;由编译器自动分配和释放。

动态数据区(Dynamic Data Area)
定义和特点:
动态数据区,通常指堆(Heap),是用于存储在运行时动态分配的数据(如通过new, malloc, calloc等分配的数据)。
程序员需要显式地申请和释放内存,这提供了更大的灵活性来管理内存使用。
变量的生命周期不是固定的,而是依赖于显式的创建和销毁。

总结
静态数据区主要用于生命周期固定、大小确定的数据,它的管理简单但缺乏灵活性。动态数据区则主要用于生命周期不定、大小可变的数据,提供了高度的灵活性,但管理起来较为复杂,且容易引发错误。正确地选择和使用这两种数据区,对于开发高效、可维护的软件应用至关重要。

16.下图属于哪种设计模式?

在这里插入图片描述
C 模板方法模式

模板方法模式将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。

不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。先制定一个顶级逻辑框架,而将逻辑的细节留给具体的子类去实现

17.某硬盘有240个磁道(最外侧磁道号为0),磁道访问请求序列为30, 60, 90, 120, 190, 150, 220,当前磁头位于第180号磁道并从外侧向内侧移动。按照SCAN调度(电梯调度)方法处理完上述请求后,磁头移过的磁道数是

磁头从一个方向移动到磁盘的尽头,然后在到达尽头时反向,直到满足所有的磁道访问请求
磁头首先移动到最近的请求磁道190,然后继续向内侧移动至220。
从220号磁道改变方向,向外侧移动。
依次访问150, 120, 90, 60, 和 30号磁道。
180->190 10磁道
190->220 30磁道
220->150 70磁道
150->120 30磁道
120->90 30磁道
90->60 30磁道
60->30 30磁道
一共 230磁道

21.下列选项中只要其中一个表中存在匹配,则返回行的SQL JOIN的类型是

full outer join
如果要返回的行只需在任何一个表中存在匹配,那么应该使用全外连接
全外连接会包含两个表中所有的记录,无论它们在另一个表中是否有匹配

22.已知某个Mysql数据库里面有一个帖子表,这个表会存储帖子的各种信息,请问以下哪种情况对应的列不适合建索引

作者id,有争议,先别管

23.下列哪些运算不会排序

between属于条件过滤,而非排序
聚合函数比如sum,avg,count等,可以排序也可以不排序,可以显示指定排序

SELECT department_id, COUNT(*) AS num_employees
FROM employees
GROUP BY department_id
ORDER BY num_employees DESC;

在这里,COUNT(*) 聚合函数用于计算每个部门的员工数,而 ORDER BY num_employees DESC 明确指示根据员工数降序排序。排序是由 ORDER BY 子句控制的。

;