美团-后端笔试真题,2024/03/24

591 阅读13分钟

思路借鉴于2024.3.24美团暑期实习笔试AK指南(附算法模版&多语言题解&类似题目推荐) 今天美团笔试,难!(0323真题解析),主要是针对真题的二次解释,第一题的输入有点小问题所有程序仅通过样例,可能存在错误,最后一题暂时没用java重写。

难度:第一、二道是简单题,第三道是略难的简单题,第四道思路比较复杂,是中等偏困难的题。

第一题有点小问题,对接受输入有要求,注意使用scanner.next()来接收,再依次赋给a[i][j]。

T1 01矩阵

小美拿到了一个n行m列的矩阵,她想知道该矩阵有多少个 2*2 的子矩形满足 1 和 0 的数量相等?

输入描述

第一行输入两个正整数n,m用空格隔开。

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

2<=n,m<=100

输出描述

一个整数,代表 1 和 0 的数量相等的 2*2 的子矩形数量。

示例 1

输入

2 3
110
010

输出

1

说明

两个 2*2 的子矩形,只有一个是合法的。

思路与代码

模拟题。

由于子矩阵固定大小是22,因此不需要前缀和,直接模拟即可。只需要保证每一个22的子矩阵的和是2即可。

import java.util.Scanner;
​
public class T1 {
    public static void main(String[] args) {
        // 注意2 <= n,m <= 100,至少都会存在一个2x2的子数组

        Scanner sc = new Scanner(System.in);

        int n = sc.nextInt();  // 读取整数 n,代表二维数组的行数

        int m = sc.nextInt();  // 读取整数 m,代表二维数组的列数

        char[][] a = new char[n][m]; 创建二维字符数组 a,大小为 n x m

        int cnt = 0;

        for (int i = 0; i<n; i++){
            String s = sc.next(); // 先接受字符串输入
            for(int j = 0; j<m; j++){
                a[i][j] = s.charAt(j); // 再赋值给a[i][j]
            }
        }

        for (int i = 0; i<n-1; i++){
            for(int j = 0; j<m-1; j++){
                if(Character.getNumericValue(a[i][j])
                        + Character.getNumericValue(a[i+1][j])
                        + Character.getNumericValue(a[i][j+1])
                        + Character.getNumericValue(a[i+1][j+1]) == 2){
                    cnt++;
                }
            }
        }
        System.out.println(cnt);
    }
}

T2 小美的回文子串

小美有一个长为 n的字符串s,她希望删除尽可能少的字符,使得字符串不含长度为偶数的回文子串

她想知道她最少要删除几个字符,请你帮帮她吧。

输入描述

输入包含两行。

第一行一个正整数n(1<=n<=10^5),表示字符串s 的长度。

第二行一个长为n字符串s。

输出描述

输出包含一行一个整数,表示最少删除数量。

补充说明

子串:一个字符串从头或尾删除若干个(也可以不删)得到的结果字符串。回文:一个字符串正着读和倒着读一样,则该字符串回文。

示例 1

输入

5
aaabc

输出

2

说明

删除变为 abc即可,删除了2个字符。

示例 2

输入

1
e

输出

0

思路与代码

偶数的回文子串等价于:不能有连续的字符相等。

直接按照上面的思路模拟,只要相邻的相等就要删除。

import java.util.Scanner;
​
public class T2 {
    public static void main(String[] args) {
        // 回文子串:aba(奇数长度),abba(偶数长度)
        // 需要删除的只是偶数长度的回文子串,那么只需要 当前字符 = 前一位字符时,删除当前字符就可以,例:abba -> aba
        Scanner scanner = new Scanner(System.in); // 创建 Scanner 对象,用于读取输入
        int n = scanner.nextInt(); // 读取整数 n,代表字符串长度
        String s = scanner.next(); // 读取字符串 s,例:"aaabc"
        char c = ' '; // 初始化字符 c 为空格
        int ans = 0; // 初始化答案为 0
​
        // 遍历字符串 s 中的字符
        for(int i = 0; i < n; i++) {
            char a = s.charAt(i); // 获取当前位置的字符 a
            if(a == c) { // 如果当前字符与前一个字符相同
                ans++; // 增加删除次数
            }
            c = a; // 更新前一个字符为当前字符
        }
        System.out.println(ans); // 输出删除次数
    }
}

T3 小美的元素交换

小美拿到了一个排列,其中初始所有元素都是红色,但有一些元素被染成了白色。

小美每次操作可以选择交换任意两个红色元素的位置。她希望操作尽可能少的次数使得数组变成非降序,你能帮帮她吗?

排列是指:一个长度为n的数组,其中 1 到n每个元素恰好出现了一次。

输入描述

第一行输入一个正整数n,代表数组的长度。

第二行输入n个正整数ai,代表数组的元素。

第三行输入一个长度为n的字符串,代表数组元素的染色情况。第i个字符为'R'代表第i个元素被染成红色,为'W'代表初始的白色。

1<=n<=10^5

1<=ai<=n

输出描述

如果无法完成排序,请输出 -1。否则输出一个整数,代表操作的最小次数。

示例 1

输入

4
1 3 2 4
WRRW

输出

1

说明

第一次操作,交换 2 和 3,数组变成[1,2,3,4]

思路与代码

哈希表。

最后的结果要求是[1,2,3,....n],那么只需要保证下标为i的元素是i+1即可,使用哈希表记录每一个数字的出现位置,不断往后交换即可。

import java.util.*;
​
public class T3 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(); // 输入数组的长度
        int[] w = new int[n]; // 创建一个整型数组w,用于存储输入的数组元素
        for(int i = 0; i < n; i++) {
            w[i] = scanner.nextInt(); // 循环读取输入的数组元素并存储到数组w中
        }
        scanner.nextLine(); // 读取输入的换行符
​
        String s = scanner.nextLine(); // 读取输入的字符串,表示颜色
        HashMap<Integer, Character> color = new HashMap<>(); // 创建HashMap,用于存储每个数字对应的颜色
        HashMap<Integer, Integer> pos = new HashMap<>(); // 创建HashMap,用于存储每个数字在数组中的位置
        for(int i = 0; i < n; i++) {
            pos.put(w[i], i); // 将数字及其位置存储到pos中
            color.put(w[i], s.charAt(i)); // 将数字及其对应的颜色存储到color中
        }
        boolean flag = true; // 标志变量,用于判断是否可以排序
        int cnt = 0; // 计数器,记录排序的次数
        for(int i = 0; i < n; i++) {
            if(w[i] == i + 1) { // 如果当前数字已经在正确的位置上,则跳过
                continue;
            }
            // w[i] != i+1,需要交换w[i]的位置和i+1的位置,(i,w[i])和(pos1,i+1)
            int pos1 = pos.get(i + 1); // 获取数字i+1在数组中的位置pos1
            if(color.get(i + 1) == 'W' || color.get(w[i]) == 'W') { // 如果数字i+1或当前数字的颜色为白色,则无法交换,退出循环
                flag = false;
                break;
            }
            // 交换位置(值所对应的),即更新pos<值,位置>
            pos.put(w[i], pos1); // 更新当前数字的位置(pos1,w[i])
            pos.put(i + 1, i); // 更新数字i+1的位置(i+1,i)
            // 交换数字i和数字i+1的位置,即交换值
            int temp = w[i];
            w[i] = w[pos1];
            w[pos1] = temp;
            cnt++; // 计数器加一
        }
        if(!flag) // 如果无法排序,则输出-1
            System.out.println(-1);
        else // 否则输出排序的次数
            System.out.println(cnt);
    }
}

T4 小美的字符串切割

小美定义一个字符串的权值为:字符串长度乘以字符的种类数。例如,"arcaea"的权值为 6*4=24

现在小美拿到了一个字符串,她希望你将该字符串切割成若干个连续子串,使得每个子串的权值不小于k。请你求出最终最多可以切割出的子串数量。

请注意,由于字符串过长,给出的字符串将是以连续段长度形式给出,例如:aabbaaa 将描述为 a(2)b(2)a(3),aaaaaaaaaaaab 将描述为 a(12)b(1)。

输入描述

第一行输入一个两个正整数 n,k,代表原字符串长度和每个子串至少应取的权值。

第二行一个仅包含小写字母、数字和括号的字符串。长度不超过 10^6。

保证所有括号内的数字之和恰好等于n。给定的每个字母后面必然包含一个括号加数字。

1<=k,n<=10^18

输出描述

如果整个字符串的权值小于k,请直接输出 -1。

否则输出一个正整数,代表可以切割的最多子串数量。

示例 1

输入

7 6
a(2)b(2)a(3)

输出

2

说明

该字符串表示为"aabbaaa",切割成"aab"和"baaa"即可。

示例 2

输入

1000000000 5
x(1000000000)

输出

200000000

说明

该字符串表示为"xxx……x",共有10^9个 x,可以切割成 200000000 个"xxxxx"

思路与代码

贪心。

题目要求切割数要尽可能多,那么对于某一段子串,我们应该满足的是:点到即可。如果且长度为x可以满足权值大于等于k,那么就不要切x+1。

例如样例a(2)b(2)a(3),需要先将输入处理成[[a,2],[b,2],[a,3]]的形式。然后对于每一段我们做如下判断:

  1. 如果当前累计的数量和之前累计的长度的权值可以达到k,那么就切割。
  2. 切割的数要满足 大于等于k,只要刚好到达即可。
import java.util.*;
​
public class T4 {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in); // 创建Scanner对象,用于从标准输入读取数据
        long n = in.nextLong(); // 读取长整型n,代表原字符串长度
        long k = in.nextLong(); // 读取长整型k,代表每个子串至少应取的权值
        in.nextLine(); // 读取换行符
​
        // 读取包含括号的字符串并分割成字符数组和长整型数组
        String[] s = in.nextLine().split("[()]"); // a 2 b 2 a 3
        char[] ch = new char[s.length / 2]; // 创建字符数组,用于存储括号内的字符
        long[] nums = new long[ch.length]; // 创建长整型数组,用于存储括号内的数字
​
        boolean[] vis = new boolean[26]; // 创建布尔数组,用于标记字符是否出现过
        long alldiff = 0; // 计数器,统计不同字符的数量
        for (int i = 0; i < s.length; i += 2) {
            ch[i / 2] = s[i].charAt(0); // 提取括号内的字符,a b a
            alldiff += vis[ch[i / 2] - 'a'] ? 0 : 1; // 统计不同字符的数量
            vis[ch[i / 2] - 'a'] = true; // 标记字符已经出现过
            nums[i / 2] = Long.parseLong(s[i + 1]); // 提取括号内的数字,2 2 3
        }
​
        if (alldiff * n < k) { // 如果整个字符串的权值小于k,则无法满足条件,输出-1并结束程序
            System.out.println(-1);
            in.close();
            return;
        }
​
        long diff = 0; // 记录当前子串中不同字符的数量
        long m = 0; // 记录当前子串中数字的总和
        long ans = 0; // 记录满足条件的数对数量
​
        Arrays.fill(vis, false); // 重置字符是否出现过的标记数组
        for (int i = 0; i < ch.length; i++) {
            int c = ch[i] - 'a'; // 计算字符对应的索引
            long j = nums[i]; // 获取字符对应的数字
​
            if (!vis[c]) { // 如果字符未出现过,则增加不同字符数量
                vis[c] = true;
                diff++;
            }
​
            if (1L * diff * (m + j) < k) { // 如果当前子串的权值不满足条件,则继续处理下一个字符
                m += j; // 加上当前数对的全部字符
                continue; // 当前不满足,下一个循环,遍历下一个数对
            }
​
            // 二分查找满足条件的最小数量
            long low = m + 1, high = m + j, idx = m + j;
            while (low <= high) {
                long mid = (low + high) >>> 1L; // low+high/2,无符号右移运算,不考虑符号位
                if (diff * mid >= k) { // 切割的字串权值 >= k
                    idx = mid; // 记录mid
                    high = mid - 1;
                } else {
                    low = mid + 1;
                }
            }
            ans++; // 增加数对数量
            long last = m + j - idx; // 计算剩余字符的数量,即[idx,m+j]的长度
            long cur = last / k; // [last,high]中都是同一字符,计算剩余字符是否满足条件的数对数量
            high = last % k; // 计算不满足k的字符数量,更新high指针,例如 7%6 = 1
            ans += cur; // 加上满足条件的数对数量
​
            diff = 0; // 重置不同字符数量
            m = 0; // 重置数字总和
            Arrays.fill(vis, false); // 重置字符是否出现过的标记数组
​
            if (high > 0) { // 如果剩余数量大于0,则重置vis[]、diff、m等,否则说明没有剩余字符串
                vis[c] = true;
                diff = 1;
                m = high; // m等于当前不满足k的剩余字符数量
            } // 进入下一个字符对
        }
        System.out.println(ans); // 输出最终满足条件的数对数量
        in.close(); // 关闭Scanner对象
​
    }
}

T5 迷路的孩子

有一棵有 n 个节点的树,小美在 s 节点,要去 t 节点。

但小美是经常迷路的孩子,她不知道该怎么走,因此她每次都会随机选择一条之前没有走过的边走,小美想知道她能到达 t 节点的概率是多少。

有多次询问,每次询问需要求出小美能到达 t 节点的概率对 10^9+7 取模后的结果。

如果最后答案为分数,则最简分式后的形式为 a/b ,其中 a 和 b 互质,那么输出整数 x 使得 b*x = a(mod 10^9+7) 且 0<=x <= 10^9+7。可以证明这样的整数 x 是唯一的。

输入描述

第一行输入一个整数 n(1<=n<=2*10^5) 表示树节点个数。

接下来 n-1 行,每行输入两个整数 u,v(1<=u,v<=n) 表示树上的边。

接下来一行,输入一个整数 q(1<=q<=2*10^5) 表示询问次数。

接下来 q 行,每行输入两个整数 s,t(1<=s,t<=n) 表示询问。

输出描述

输出 q 行,每行输出一个整数表示概率。

示例 1

输入

3
1 2
1 3
2
2 3
1 3

输出

1
500000004

说明

第1个询问:

小美有1的概率从节点2走到节点1,然后有1的概率从节点1走到节点3,因此有1的概率能到达节点3。1对1000000007取模后的结果为1。

第2个询问:

小美有1/2的概率从节点1走到节点2,有1/2的概率从节点1走到节点3,因此只有1/2的概率能到达节点3,1/2对1000000007取模后的结果为500000004。

思路与代码

LCA+概率+预处理。

思路如下:

  1. dfs预处理出每个节点的深度以及每个节点走到父节点的概率。
  2. 对于每组查询,计算s和t的LCA,这里需要用到跳跃的思维来进行优化。
    1. 如果s就是t或LCA(s, t),概率是1(因为小美已经在t或者直接从s或t单向移动到另一个点)。
    2. 如果LCA不是s也不是t,需要分别计算从s到LCA和从t到LCA的概率,然后将这两个概率相乘,并考虑在LCA点选择不同路径的影响。
// 请到公众号或知乎中查看