美团-后端笔试真题,2024/04/06,10:00

274 阅读9分钟

复盘一下真题,用java解答,所有程序仅通过样例,可能存在错误,最后一题暂时没用java重写。

思路借鉴于公众号“万诺coding”

难度:第一、二道是简单题,第三道是略难的简单题,第四道和第五道是中等偏上题。

1.最少修改次数

小美拿到了一个长度为7的字符串。她想知道将该字符串修改为"meituan"至少需要修改多少次?

每次修改,小美可以修改任意一个字符。

输入描述:

输入一个长度为7的字符串,字符串中只包含小写字母。

输出描述:

小美需要修改的次数。

样例输入

meituan

样例输出

0

思路与代码

模拟计数即可。

import java.util.Scanner;
​
public class N1 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
​
        // 输入目标字符串
        String target = "meituan";
​
        // 输入待修改的字符串
        String string = scanner.nextLine();

        int count = 0;
        for (int i = 0; i < string.length(); i++) {
            if (string.charAt(i) != target.charAt(i)) {
                count++;
            }
        }
​
        System.out.println(count);
​
        scanner.close();
    }
}

2.计算实数

小美拿到了一个由复数组成的数组,她想知道其中有多少个实数?

实数:有理数和无理数的总称。其中无理数是无限不循环小数,有理数包括整数和分数。

输入描述:

第一行输入一个正整数,代表数组的大小。

第二行输入n个复数,代表小美拿到的数组。

后台数据保证复数为a或者a+bi的形式,其中a和b为绝对值不超过10^9的整数。

1<= n <=10^5

输出描述:

一个整数,代表实数的数量。

样例输入

4
-5 5-i 6+3i -4+0i

样例输出

2

思路与代码

模拟题。

将所有的"+0i"和"-0i"替换成空字符串,然后判断每个子串是否包含"i"即可。

import java.util.Scanner;
​
public class N2 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
​
        // 读取数组大小
        int n = scanner.nextInt();
        scanner.nextLine(); // 消耗换行符
​
        // 读取复数数组
        String[] complexNumbers = scanner.nextLine().split(" ");
​
        int realCount = 0;
        // 将所有的"+0i"和"-0i"替换成空字符串,然后判断每个子串是否包含"i"即可。
        for (String complexNumber : complexNumbers) {
            complexNumber = complexNumber.replace("+0i", "").replace("-0i", "");
            if (!complexNumber.contains("i")) {
                realCount++;
            }
        }
​
        System.out.println(realCount);
​
        scanner.close();
    }
​
​
}

3.还原数组

小美有一个由 n 个互不相等的正整数构成的数组 a,但她一不小心把 a 弄丢了,他想要重新找到 a。

好在她并不是一无所有,她还记得以下有关 a 的信息:

\1. 他完全记得数组 b 的样子,并且 b 是数组 a 删除了某个 a_i 后,剩余的部分做前缀和并打乱的结果。

\2. 他完全记得数组 c 的样子,并且 c 是数组 a 删除了某个 a_j 后,剩余的部分做前缀和并打乱的结果。

(保证两次删除的 a_i 和 a_j 不是同一个 a 中的元素)。

请你帮她还原出 a 数组吧。

补充:前缀和指一个数组的某下标之前的所有数组元素的和(包含其自身)。

输入描述

输入包含三行。

第一行一个正整数 n\ (3 <=n <=10^5),表示数组 a 的长度。

第二行 n-1 个正整数 b_i\ (1 <=b_i <=10^{14}),表示题中所述数组 b。

第二行 n-1 个正整数 c_i\ (1 <=c_i <=10^{14}),表示题中所述数组 c。

(输入保证有唯一解)

输出描述

输出一行 n 个整数,表示你还原出的 a 数组。

样例输入

4
8 18 14
15 9 1

样例输出

1 8 6 4

思路与代码

思维题。

前缀和是有序的,因此可以直接排序。如此一来可以利用前缀和还原数组,我们以第一个数组nums1为基准,遍历第二个数组nums2,如果找到一个数字x是不存在于nums1的,假设我们定义x的下标是i,那么我们可以确定,x插入到nums1中,nums2[i+1]对应位置的前面。

import java.util.*;
​
public class N3 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] b = new int[n-1];
        int[] c = new int[n-1];
​
        for (int i = 0; i < n-1; i++) {
            b[i] = scanner.nextInt();
        }
​
        for (int i = 0; i < n-1; i++) {
            c[i] = scanner.nextInt();
        }
​
        // Sort the arrays
        Arrays.sort(b);
        Arrays.sort(c);
​
        int[] nums1 = new int[n - 1];
        int[] nums2 = new int[n - 1];
        nums1[0] = b[0];
        nums2[0] = c[0];
​
        for (int i = 1; i < n - 1; i++) {
            nums1[i] = b[i] - b[i - 1];
            nums2[i] = c[i] - c[i - 1];
        }
​
        Set<Integer> set = new HashSet<>();
        for (int num : nums1) {
            set.add(num);
        }
​
        for(int i = 0; i < n-1; i++){
            // 如果nums1 不包含 nums2[i],则说明这个值是缺失的那个并且唯一
            if(!set.contains(nums2[i])){ // 8 18 14(6,4) 15 9 1(8,6)
                if(i == n-1){
                    // 先打印完nums1,再打印nums2[i],例如1 8 6 4
                    for (int num : nums1) {
                        System.out.print(num + " ");
                    }
                    // 再打印nums2[i]
                    System.out.print(nums2[i]);
                    break;
                }else{ // 没到末尾就出现了唯一的缺少值
                    int index = Arrays.asList(nums1).indexOf(nums2[i + 1]);
                    // 先打印nums1[0][index]的结果,再打印nums2[i],再打印nums1[index]到nums1末尾
                    for (int j = 0; j <= index; j++) {
                        System.out.print(nums1[j] + " ");
                    }
                    // 再打印nums2[i]
                    System.out.print(nums2[i] + " ");
                    // 最后打印nums1[index]到nums1末尾
                    for (int j = index + 1; j < nums1.length; j++) {
                        System.out.print(nums1[j] + " ");
                    }
                    break;
                }
            }
        }
​
    }
}

4.小美点外卖

小美经常去美团上点外卖,她给自己规划了每天会在哪些商家中选择一家来点。

由于怕吃腻,小美决定不会连续两天点同一个商家。现在请你判断小美有多少种不同的选择方案?

输入格式

第一行输入一个正整数n,代表小美点外卖的天数。

接下来的n行,每行输入一个长度不超过 20 的字符串,代表小美每天的选项。相同的字符代表同一个商家。

1<=n <=10^5

保证每个字符串内的字符都不同。

输出格式

输出一个整数,代表小美点外卖的方案数。由于答案过大,请对10^9+7取模。

示例1

输入

2
ab
bcd

输出

5

说明

方案 1:第一天点商家 a,第二天点商家 b。

方案 2:第一天点商家 a,第二天点商家 c。

方案 3:第一天点商家 a,第二天点商家 d。

方案 4:第一天点商家 b,第二天点商家 c。

方案 5:第一天点商家 b,第二天点商家 d。

思路与代码

动态规划。

每一天的选择是:选择当天的任意一个商家,只要满足与上一天的商家不一样即可。

f[i,j] 含义为,上一天选择的商家是j,第i天往后考虑的方案数。

f[i,j] = Σf[i+1,k],for k in stores[i] && k!=j

import java.util.HashMap;
import java.util.Scanner;
​
public class N4 {
    static int mod = 1000000007; // 定义模数
    static HashMap<String, Integer> dp = new HashMap<>(); // 用于记忆化搜索的HashMap,避免重复计算
​
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt(); // 读取n的值
        scanner.nextLine(); // 读取换行符
        String[] stores = new String[n]; // 创建存储每天的商店名称的数组
        for (int i = 0; i < n; i++) {
            stores[i] = scanner.nextLine(); // 读取每天的每个商店的名称
        }
        scanner.close(); // 关闭Scanner
        // (当前商店的下标,前一个选择的字符,所有商店可选择的值)
        int result = dfs(0, "", stores); // 计算结果
        System.out.println(result); // 输出结果
    }
​
    // 深度优先搜索函数(当前访问的天数i,前一天选择的商店c,可选择的商店数组)
    static int dfs(int i, String c, String[] stores) {
        // 如果已经访问完所有商店,则返回1表示这是一种有效的访问方式
        if (i == stores.length) return 1;
​
        // 如果已经计算过这个状态(当前商店索引i和当前字符c),则返回已经计算过的结果,避免重复计算
        if (dp.containsKey(i + "," + c)) return dp.get(i + "," + c);
​
        int res = 0; // 用于存储当前状态的结果
​
        // 如果当前字符不为空(表示上一个商店已经选择了一个字符),进入以下循环
        if (!c.isEmpty()) {
            // 遍历当前商店的字符
            for (int j = 0; j < stores[i].length(); j++) {
                // 如果当前商店字符与前一个字符不同,可以选择这个字符继续访问下一个商店
                if (stores[i].charAt(j) != c.charAt(0)) {
                    // 递归调用dfs函数,更新结果并取模运算(下一个商店,当前商店选择的值,商店的可选择数组)
                    res += dfs(i + 1, Character.toString(stores[i].charAt(j)), stores);
                    res %= mod;
                }
            }
        } else { // 如果当前字符为空(表示还未选择字符),进入以下循环
            // 遍历当前商店的字符
            for (int j = 0; j < stores[i].length(); j++) {
                // 递归调用dfs函数,更新结果并取模运算(下一个商店,当前商店选择的值,商店的可选择数组)
                res += dfs(i + 1, Character.toString(stores[i].charAt(j)), stores);
                res %= mod;
            }
        }
​
        // 将当前状态的结果存入记忆化搜索HashMap中,以便后续查询
        dp.put(i + "," + c, res);
​
        // 返回当前状态的结果
        return res;
    }
​
}

5.获胜的概率

在一个n行m列的矩阵中,小美初始在第x_1行第y_1列的位置,小团初始在x_2行第y_2列的位置。

小美每一步可以向左下、下、右下三个方向走一个格子,小团可以向左上、上、右上三个方向走一个格子。

小美先行动,小团后行动。如果两人在同一个格子相遇了,那么她们将结束游戏并获胜。如果最终都没在同一个格子,则她们失败。

但是两人都不知道对方的位置,因此她们会随机选择一个方向前进(如果在最左那一列,则不能往左上/左下。如果在最右那一列,则不能往右上/右下)。请你计算小美和小团最终获胜的概率。

输入描述

输入六个正整数n,m,x_1,y_1,x_2,y_2,用空格隔开。分别代表矩阵的行数、列数,以及小美和小团的初始位置。

1<=n,m <=3000

1<=x_1,x_2 <=n

1<=y_1,y_2 <=m

输出描述

小美和小团最终获胜的概率。可以证明,这个答案一定是一个有理数,你需要输出其对10^9+7取模的结果。

分数取模的定义:假设答案是x/y,那么其对p取模的答案是找到一个整数a满足a∈[0,p-1]且a*y对p取模等于x。

示例1

输入

3 3 1 2 2 2

输出

333333336

思路与代码

动态规划。

dp[i,j]的含义是:走到(i,j)的概率。需要注意的是,对于非边缘列(1 < j < m),小美向左下、下、右下移动的概率都是相等的,即每个方向的概率为当前位置概率除以3。因为非边缘列的位置有三个可移动的方向。对于边缘列(j == 1 或 j == m),只有两个方向可以选择,每个方向的概率为当前位置概率除以2。

对于小美和小团分别跑一次dp。

由于最终相遇必然是在中点,我们枚举累乘dp1i和dp2i,并且累加这个答案即可。

【逆元大家需要把模板背下来】

// 请到公众号去查看