2021届秋季校招笔试真题

【简单】

meituan-001. 小美的用户名

小美是美团的前端工程师,为了防止系统被恶意攻击,小美必须要在用户输入用户名之前做一个合法性检查,一个合法的用户名必须满足以下几个要求:

用户名的首字符必须是大写或者小写字母。
用户名只能包含大小写字母,数字。
用户名需要包含至少一个字母和一个数字。
如果用户名合法,请输出 “Accept”,反之输出 “Wrong”。
格式:

输入:

  • 输入第一行包含一个正整数 T,表示需要检验的用户名数量。
  • 接下来有 T 行,每行一个字符串 s,表示输入的用户名。
    输出:
  • 对于每一个输入的用户名 s,请输出一行,即按题目要求输出一个字符串。

示例:

输入:

5
Ooook
Hhhh666
ABCD
Meituan
6666

输出:

Wrong
Accept
Wrong
Wrong
Wrong

思路:
这题还是简单的,用ASCII值挨个挨个按照要求比较就行

代码实现:

import java.io.*;

public class Main {
    public static boolean isValidName(String name) {
        // 用户名需要包含至少一个字母和一个数字。 
        if (name == null || name.length() < 2) {
            return false;
        }
        // 用户名的首字符必须是大写或者小写字母。
        if (!Character.isLetter(name.charAt(0))) {
            return false;
        }
        // 用户名只能包含大小写字母,数字。
        boolean flagLetter = false, flagDigit = false;
        for (char ch : name.toCharArray()) {
            if (Character.isLetter(ch)) {
                flagLetter = true;
            } else if (Character.isDigit(ch)) {
                flagDigit = true;
            } else {
                return false;
            }
        }
        return flagLetter && flagDigit;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
        int n = Integer.parseInt(reader.readLine());
        for (int i = 0; i < n; ++i) {
            if (isValidName(reader.readLine())) {
                writer.write("Accept\n");
            } else {
                writer.write("Wrong\n");
            }
        }
        writer.close();
        reader.close();
    }
}

meituan-003. 小美的跑腿代购

小美的一个兼职是美团的一名跑腿代购员,她有 n 个订单可以接,订单编号是 1~n ,但是因为订单的时效性,他只能选择其中 m 个订单接取,精明的小美当然希望自己总的获利是最大的,已知,一份订单会提供以下信息,跑腿价格 v ,商品重量 w kg,商品每重 1kg ,代购费用要加 2 元,而一份订单可以赚到的钱是跑腿价格和重量加价之和。小美可是开兰博基尼送货的人,所以自然不会在意自己会累这种事情。请问小美应该选择哪些订单,使得自己获得的钱最多。
请你按照选择的订单编号的从小到大顺序,如果存在多种方案,输出订单编号字典序较小的方案。

格式:

输入:

  • 输入第一行包含两个正整数 n,m,表示订单的数量和小美可以接的订单数量。
  • 接下来有 n 行,第 i 行表示 i-1 号订单的信息。每行有两个正整数 v 和 w ,表示一个订单的跑腿价格和商品重量。
    输出:
  • 输出包含 m 个 1~n 之间的正整数,中间用空格隔开,表示选择的订单编号。
    示例:

输入:

5 2
5 10
8 9
1 4
7 9
6 10

输出:

2 5

思路:
就是把所有的跑腿的钱都算出来然后把最多的m个单子序号从前往后打印出来

代码实现:

import java.io.*;
import java.util.PriorityQueue;

public class Solution {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
        String[] firstLine = reader.readLine().split(" ");
        int n = Integer.parseInt(firstLine[0]);
        int m = Integer.parseInt(firstLine[1]);
        // int[0]总价,int[1]订单索引
        PriorityQueue<int[]> queue = new PriorityQueue<>((e1, e2) -> {
            if (e1[0] == e2[0]) {
                return Integer.compare(e1[1], e2[1]); // 总价相同则按索引正序
            } else {
                return Integer.compare(e2[0], e1[0]); // 按总价倒序
            }
        });
        String[] orderLine;
        int value, weight;
        for (int i = 0; i < n; i++) {
            orderLine = reader.readLine().split(" ");
            value = Integer.parseInt(orderLine[0]);
            weight = Integer.parseInt(orderLine[1]);
            int[] e = new int[2];
            e[0] = value + weight * 2;
            e[1] = i;
            queue.offer(e);
        }
        boolean[] flag = new boolean[n];
        for (int i = 0; i < m; i++) {
            int[] e = queue.poll();
            assert e != null;
            flag[e[1]] = true;
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < n; i++) {
            if (flag[i]) {
                builder.append(i + 1).append(" ");
            }
        }
        writer.write(builder.toString());
        writer.flush();
        reader.close();
        writer.close();
    }
}

meituan-006. 小团的神秘暗号

小团深谙保密工作的重要性,因此在某些明文的传输中会使用一种加密策略,小团如果需要传输一个字符串 S ,则他会为这个字符串添加一个头部字符串和一个尾部字符串。头部字符串满足至少包含一个 “MT” 子序列,且以 T 结尾。尾部字符串需要满足至少包含一个 “MT” 子序列,且以 M 开头。例如 AAAMT 和 MAAAT 都是一个合法的头部字符串,而 MTAAA 就不是合法的头部字符串。很显然这样的头尾字符串并不一定是唯一的,因此我们还有一个约束,就是 S 是满足头尾字符串合法的情况下的最长的字符串。
很显然这样的加密策略是支持解码的,给出一个加密后的字符串,请你找出中间被加密的字符串 S 。

格式:

输入:

  • 输入第一行是一个正整数 n ,表示加密后的字符串总长度。
  • 输入第二行是一个长度为 n 的仅由大写字母组成的字符串 T 。
    输出:
  • 输出仅包含一个字符串 S 。

示例:

输入:

10
MMATSATMMT

输出:

SATM

思路:

  • 按照他的题意从前往后遍历,找到满足条件的最短头部字符串的尾部下标
  • 按照他的题意从后往前便利,找到满足条件的最短尾部字符串的头部下标
  • 返回中间子字符串

代码实现

import java.io.*;
public class Main {
    //头部字符串满足至少包含一个 “MT” 子序列,且以 T 结尾
    //尾部字符串需要满足至少包含一个 “MT” 子序列,且以 M 开头。
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));

        //输入
        int n = Integer.parseInt(reader.readLine());
        String T = reader.readLine();

        //TODO
        writer.write(recoveryString(n, T));

        //资源关闭
        writer.flush();
        writer.close();
        reader.close();
    }

    public static String recoveryString(int n, String T) {
        char[] chars = T.toCharArray();
        boolean headM = false;
        boolean tailT = false;
        int start = 0;
        int end = 0;
        for (int i = 0; i < n; i++) {
            if (chars[i] == 'M') {
                headM = true;
            }
            if (chars[i] == 'T' && headM == true) {
                start = i + 1;
                break;
            }
        }

        for (int i = n - 1; i > 0; i--) {
            if (chars[i] == 'T') {
                tailT = true;
            }
            if (chars[i] == 'M' && tailT == true) {
                end = i;
                break;
            }
        }
        return T.substring(start, end);
    }
}

【中等】

meituan-002. 小美的仓库整理

小美是美团仓库的管理员,她会根据单据的要求按顺序取出仓库中的货物,每取出一件货物后会把剩余货物重新堆放,使得自己方便查找。已知货物入库的时候是按顺序堆放在一起的。如果小美取出其中一件货物,则会把货物所在的一堆物品以取出的货物为界分成两堆,这样可以保证货物局部的顺序不变。
已知货物最初是按 1~n 的顺序堆放的,每件货物的重量为 w[i] ,小美会根据单据依次不放回的取出货物。请问根据上述操作,小美每取出一件货物之后,重量和最大的一堆货物重量是多少?

格式:

输入:

  • 输入第一行包含一个正整数 n ,表示货物的数量。
  • 输入第二行包含 n 个正整数,表示 1~n 号货物的重量 w[i] 。
  • 输入第三行有 n 个数,表示小美按顺序取出的货物的编号,也就是一个 1~n 的全排列。
    输出:
  • 输出包含 n 行,每行一个整数,表示每取出一件货物以后,对于重量和最大的一堆货物,其重量和为多少。

示例:

输入:

5
3 2 4 4 5
4 3 5 2 1

输出:

9
5
5
3
0
解释:
原本的状态是 {{3,2,4,4,5}} ,取出 4 号货物后,得到 {{3,2,4},{5}} ,第一堆货物的和是 9 ,然后取出 3 号货物得到 {{3,2}{5}} ,此时第一堆和第二堆的和都是 5 ,以此类推。

思路:
暴力法自然就是从前往后遍历,将两堆的最大值输出。但是这个做法的时间复杂度位O(n²)。
但是我们还有更好的选择,顺序遍历是取出货物,那么我们可以选择从前往后遍历放入货物。
然后将结果反向输出不就是需要的答案了嘛

import java.io.*;

public class Main {
    private static int max = 0; // 当前最大重量和
    private static int[] parent; // parent[i]表示编号为i的货物的父节点索引,若parent[i]=i则表示此为一个根节点
    private static int[] heapWeight; // 若第i号货物是一个根节点,则heapWeight[i]表示此堆货物的重量和,否则值为0

    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
        int n = Integer.parseInt(reader.readLine()); // 货物数目
        String[] weight = reader.readLine().split(" ");
        String[] number = reader.readLine().split(" ");
        int[] w = new int[n]; // 货物重量
        int[] putSn = new int[n]; // 放入顺序,即倒序的取出顺序
        parent = new int[n];
        heapWeight = new int[n];
        for (int i = 0; i < n; i++) {
            w[i] = Integer.parseInt(weight[i]);
            putSn[i] = Integer.parseInt(number[i]);
            parent[i] = i; // 初始状态时,各编号货物独自成堆
        }
        boolean[] used = new boolean[n]; // 表示以第i号货物是否已被放入
        int[] result = new int[n + 1]; // 每次放入操作后的最大重量和
        int putIndex;
        for (int i = n - 1; i > 0; i--) { // 开始放入(不需计算最后一次)
            putIndex = putSn[i] - 1; // 索引=序号-1
            used[putIndex] = true;
            // 先自成一个集合
            heapWeight[putIndex] = w[putIndex];
            max = Math.max(heapWeight[putIndex], max);
            // 若右边元素已被放入,则其必是一个根节点,并入当前元素(排除最后一号)
            if (putIndex + 1 < n && used[putIndex + 1]) {
                union(putIndex, putIndex + 1);
            }
            // 若左边元素已被放入,则将当前元素合并过去(排除第一号)
            if (putIndex > 0 && used[putIndex - 1]) {
                union(putIndex - 1, putIndex);
            }
            result[i] = max;
        }
        for (int i = 1; i <= n; i++) { // 多输出一个第n次取出,即第0次放入
            writer.write(result[i] + "\n");
        }
        writer.flush();
        reader.close();
        writer.close();
    }

    // 合并:r号货物所率领的堆 -> l号货物所在的堆,最终根节点为l号货物所属的根节点
    private static void union(int l, int r) {
        int lRoot = find(l);
        parent[r] = lRoot;
        heapWeight[lRoot] += heapWeight[r];
        heapWeight[r] = 0;
        max = Math.max(heapWeight[lRoot], max);
    }

    // 查询i号货物的根节点
    private static int find(int i) {
        while (parent[i] != i) {
            i = find(parent[i]);
        }
        return i;
    }

}

【困难】

meituan-004. 小团的复制粘贴

小团是一个莫得感情的 CtrlCV 大师,他有一个下标从 1 开始的序列 A 和一个初始全部为 -1 序列 B ,两个序列的长度都是 n 。他会进行若干次操作,每一次操作,他都会选择 A 序列中一段连续区间,将其粘贴到 B 序列中的某一个连续的位置,在这个过程中他也会查询 B 序列中某一个位置上的值。
我们用如下的方式表示他的粘贴操作和查询操作:
粘贴操作:1 k x y,表示把 A 序列中从下标 x 位置开始的连续 k 个元素粘贴到 B 序列中从下标 y 开始的连续 k 个位置上。原始序列中的元素被覆盖。(注意:输入数据可能会出现粘贴后 k 个元素超出 B 序列原有长度的情况,超出部分可忽略)
查询操作:2 x,表示询问B序列下标 x 处的值是多少。

格式:

输入:

  • 输入第一行包含一个正整数 n ,表示序列 A 和序列 B 的长度。
  • 输入第二行包含 n 个正整数,表示序列 A 中的 n 个元素,第 i 个数字表示下标为 i 的位置上的元素,每一个元素保证在 10^9 以内。
  • 输入第三行是一个操作数 m ,表示进行的操作数量。
  • 接下来 m 行,每行第一个数字为 1 或 2 ,具体操作细节详见题面。
    输出:
  • 对于每一个操作 2 输出一行,每行仅包含一个正整数,表示针对某一个询问的答案。

示例 1:

输入:

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

输出:

-1
-1
-1
4

示例 2:

输入:

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

输出:

-1
4
-1
-1
2
3
4

思路:
这种不停覆盖的题目思路,都差不多——>线段树。这道题其实不难,难在优化。

代码实现:

import java.io.*;
import java.util.*;
import java.lang.*;
public class Main {
    public static void main(String[] args) throws Exception {
        FastScanner fs = new FastScanner();
        int n = fs.nextInt();
        int[] a = fs.readArray(n);
        int m = fs.nextInt();
        SegTree tree = new SegTree(n + 1);
        for (int i = 0; i < m; i++) {
            int type = fs.nextInt();
            if (type == 1) {
                int k = fs.nextInt(), x = fs.nextInt(), y = fs.nextInt();
                k = Math.min(k, Math.min(n - y + 1, n - x + 1));
                tree.update(y, y + k - 1, 1, n, 1, x);
            } else {
                int x = fs.nextInt();
                // out.println(tree.search(x, x, 1, n, 1));
                out.println((tree.search(x, x, 1, n, 1) != -1 ? a[tree.search(x, x, 1, n, 1) - 1] : -1));
            }
        }
        

        out.flush();
        out.close();
    }
    public static class SegTree {// 线段树
        int n;
        int[] val;// 值
        int[] mark;// 懒标记
        public SegTree(int _n) {
            n = _n;
            val = new int[n << 2];
            mark = new int[n << 2];
            Arrays.fill(val, -1);
        }
        public void build(int lo, int hi, int p, int[] a) {
            if (lo == hi) {
                val[p] = a[lo];
                return;
            }
            int mid = (lo + hi) / 2;
            build(lo, mid, 2 * p, a);
            build(mid + 1, hi, 2 * p + 1, a);
            // pushup
            val[p] = val[2 * p] + val[2 * p + 1];
        }
        public void update(int lo, int hi, int left, int right, int p, int c) {
            if (left >= lo && right <= hi) {
                val[p] = c;
                mark[p] = c;// 打上懒标记,表示从c开始
                return;
            }
            int mid = (left + right) / 2;
            int num = mid + 1 - left;
            if (mark[p] != 0 && left != right) {
                pushDown(p, num);
            }
            if (lo <= mid) update(lo, hi, left, mid, 2 * p, c);
            if (hi > mid) {
                if (lo > mid) update(lo, hi, mid + 1, right, 2 * p + 1, c);
                else update(lo, hi, mid + 1, right, 2 * p + 1, c + Math.min(mid + 1 - lo, num));
            }
        }
        public void pushDown(int p, int num) {
            val[2 * p] = mark[p];
            val[2 * p + 1] = mark[p] + num;
            mark[2 * p] = mark[p];
            mark[2 * p + 1] = mark[p] + num;
            mark[p] = 0;
        }
        public int search(int lo, int hi, int left, int right, int p) {
            if (left >= lo && right <= hi) {
                return val[p];
            }
            int mid = (left + right) / 2;
            int num = mid + 1 - left;
            if (mark[p] != 0 && left != right) {
                pushDown(p, num);
            }
            int ans = 0;
            if (lo <= mid) ans += search(lo, hi, left, mid, 2 * p);
            if (hi > mid) ans += search(lo, hi, mid + 1, right, 2 * p + 1);
            return ans;
        }
    }
    public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
    public static class FastScanner {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer("");
        String next() {
            while (!st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }
        int nextInt() {
            return Integer.parseInt(next());
        }
        long nextLong() {
            return Long.parseLong(next());
        }
        int[] readArray(int n) {
            int[] a = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = nextInt();
            }
            return a;
        }
        long[] readArrayL(int n) {
            long[] a = new long[n];
            for (int i = 0; i < n; i++) {
                a[i] = nextLong();
            }
            return a;
        }
    }
}

meituan-005. 小美的区域会议

小美是美团总部的高管,她想要召集一些美团的区域负责人来开会,已知美团的业务区域划分可以用一棵树来表示,树上有 n 个节点,每个节点分别代表美团的一个业务区域,每一个区域有一个负责人,这个负责人的级别为 A[i]
已知小美召集人员开会必须满足以下几个条件:
1.小美召集的负责人所在的区域必须构成一个非空的连通的图,即选取树上的一个连通子图。
2.这些负责人中,级别最高的和级别最低的相差不超过 k 。
请问小美有多少种召集负责人的方式,当且仅当选取的集合不同时我们就认为两种方式不同。由于方案数可能非常大,所以请对 10^9+7 取模。

格式:

输入:

  • 输入第一行包含两个整数 n 和 k ,表示区域的数量,和不能超过的级别。
  • 接下来有 n-1 行,每行有两个正整数 a 和 b ,表示 a 号区域和 b 号区域有一条边。
  • 最后一行有 n 个整数,第 i 个整数表示 i 号区域负责人的级别。
    输出:
  • 输出仅包含一个整数,表示可以选择的方案数对 10^9+7 取模之后的结果。

示例:

输入:

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

输出:

15
解释:显然一个区域的方案有 {1},{2},{3},{4},{5},两个区域的方案有 4 个,三个区域的方案有 3 个,四个区域的方案有 2 个,五个区域的方案有 1 个,共 15 个。

思路:
今儿有点累了,明儿再看吧。这美团题目太难了吧,我果然去不了大厂了嘛

心得

  • 2022-05-05
    实际上,做这种大厂笔试题,我们发现,如果想要优化,在输入输出方面可以进行优化,如果是我之前自己做,就是用new Scanner。但是看了大佬的答案,我才知道还可以用IO流,更快更简洁↓
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));//输入
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
int n = Integer.parseInt(reader.readLine());//第一行
for (int i = 0; i < n; ++i) {
    if (isValidName(reader.readLine())) {
        writer.write("Accept\n");//输入的内容
    } else {
        writer.write("Wrong\n");
    }
}
writer.close();
reader.close();
  • 2022-05-05
    可以自己做一些输入方面的封装,以及一些数据结构的封装,方便使用
  1. 输入的封装
//输出
public static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
//输入
public static class FastScanner {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringTokenizer st = new StringTokenizer("");

    String next() {
        while (!st.hasMoreTokens()) {
            try {
                st = new StringTokenizer(br.readLine());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return st.nextToken();
    }

    int nextInt() {
        return Integer.parseInt(next());
    }

    long nextLong() {
        return Long.parseLong(next());
    }

    int[] readArray(int n) {
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = nextInt();
        }
        return a;
    }

    long[] readArrayL(int n) {
        long[] a = new long[n];
        for (int i = 0; i < n; i++) {
            a[i] = nextLong();
        }
        return a;
    }
}
  1. 线段树的封装
public static class SegTree {// 线段树
    int n;
    int[] val;// 值
    int[] mark;// 懒标记

    public SegTree(int _n) {//构造器
        n = _n;
        val = new int[n << 2];
        mark = new int[n << 2];
        Arrays.fill(val, -1);
    }

    public void build(int lo, int hi, int p, int[] a) {
        if (lo == hi) {
            val[p] = a[lo];
            return;
        }
        int mid = (lo + hi) / 2;
        build(lo, mid, 2 * p, a);
        build(mid + 1, hi, 2 * p + 1, a);
        // pushup
        val[p] = val[2 * p] + val[2 * p + 1];
    }

    public void update(int lo, int hi, int left, int right, int p, int c) {
        if (left >= lo && right <= hi) {
            val[p] = c;
            mark[p] = c;// 打上懒标记,表示从c开始
            return;
        }
        int mid = (left + right) / 2;
        int num = mid + 1 - left;
        if (mark[p] != 0 && left != right) {
            pushDown(p, num);
        }
        if (lo <= mid) update(lo, hi, left, mid, 2 * p, c);
        if (hi > mid) {
            if (lo > mid) update(lo, hi, mid + 1, right, 2 * p + 1, c);
            else update(lo, hi, mid + 1, right, 2 * p + 1, c + Math.min(mid + 1 - lo, num));
        }
    }

    public void pushDown(int p, int num) {
        val[2 * p] = mark[p];
        val[2 * p + 1] = mark[p] + num;
        mark[2 * p] = mark[p];
        mark[2 * p + 1] = mark[p] + num;
        mark[p] = 0;
    }

    public int search(int lo, int hi, int left, int right, int p) {
        if (left >= lo && right <= hi) {
            return val[p];
        }
        int mid = (left + right) / 2;
        int num = mid + 1 - left;
        if (mark[p] != 0 && left != right) {
            pushDown(p, num);
        }
        int ans = 0;
        if (lo <= mid) ans += search(lo, hi, left, mid, 2 * p);
        if (hi > mid) ans += search(lo, hi, mid + 1, right, 2 * p + 1);
        return ans;
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

走出半生仍是少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值