美团0810秋招笔试真题解析(秋招第一批笔试) 小美的密码 小美准备登录美团,需要输入密码,小美忘记了密码,只记得密码可能是 n个字符串中的一个。小美会按照密码的长度从小到大依次尝试每个字符串,对于相同长度的字符串,小美随机尝试,并且相同的密码只会尝试一次。小美想知道,她最少需要尝试多少次才能登录成功,最多需要尝试多少次才能登录成功。
小美不会重新尝试已经尝试过的字符串。成功登录后会立即停止尝试。
输入描述
第一行输入一个整数 n(1<=n<=1000)代表密码字符串的个数。
第二行输入一个只由小写字母组成的字符串 s(1<=|s|<=1000)代表正确的密码。
接下来 n 行,每行输入一个长度不超过 1000的字符串,代表小美记得的密码。
输出描述
在一行上输出两个整数,表示最少和最多尝试次数。
示例 1
输入
输出
说明
小美可能按照 [“ab”, “ac”, “abc”] 的顺序尝试,第一次尝试成功,也可能按照 [“ac”, “ab”, “abc”] 的顺序尝试,第二次尝试成功。
小美在尝试 “ac” 发现不正确后不会继续尝试 “ac”。
思路与代码
HashMap+HashSet
由于是从长度小的开始,所以记录每个长度的密码都有多少个就好。
最优选择就是直接一次猜中对应长度的密码
反之最差就是最后一次猜中对应长度的密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private static void T1 () { Scanner in = new Scanner (System.in); int len = in.nextInt(); String password = in.next(); HashSet<String> set = new HashSet <>(); HashMap<Integer, Integer> map = new HashMap <>(); for (int i = 0 ; i < len; i++) { String str = in.next(); if (!set.contains(str)) { map.merge(str.length(), 1 , Integer::sum); set.add(str); } } int rightLen = password.length(); int start=0 ; for (int i = 1 ; i < rightLen; i++) { start+=map.getOrDefault(i,0 ); } System.out.println((start + 1 )+" " +(start+map.get(rightLen))); }
小美的数组删除 小美有一个长度为 n 的数组 a1,a2,….,an ,他可以对数组进行如下操作:
● 删除第一个元素 a1,同时数组的长度减一,花费为 x。
● 删除整个数组,花费为 k*MEX(a) (其中 MEX(a) 表示 a 中未出现过的最小非负整数。例如 [0,1,2,4] 的 MEX 为 3 )。
小美想知道将 a 数组全部清空的最小代价是多少,请你帮帮他吧。
输入描述
每个测试文件均包含多组测试数据。第一行输入一个整数 T(1<=T<=1000) 代表数据组数,每组测试数据描述如下:
第一行输入三个正整数 n,k,x(1<=n<=2*10^5, 1<=k,x<=10^9) 代表数组中的元素数量、删除整个数组的花费系数、删除单个元素的花费。
第二行输入 n 个整数 a1,a2,….,an(0<=ai<=n),表示数组元素。
除此之外,保证所有的 n 之和不超过 2*10^5。
输出描述
对于每一组测试数据,在一行上输出一个整数表示将数组中所有元素全部删除的最小花费。
示例 1
输入
输出
说明
1 2 3 4 5 6 7 若不执行操作一就全部删除,MEX{4,5,2,3,1,0} = 6,花费 18 ; 若执行一次操作一后全部删除,MEX{5,2,3,1,0} = 4,花费 3+12; 若执行两次操作一后全部删除,MEX{2,3,1,0} = 4,花费 6+12 ; 若执行三次操作一后全部删除,MEX{3,1,0} = 2,花费 9+6 ; 若执行四次操作一后全部删除,MEX{1,0} = 2,花费 12+6 ; 若执行五次操作一后全部删除,MEX{0} = 1,花费 15+3; 若执行六次操作一,MEX{} = 0,花费 18;
思路和代码
枚举删除的次数。同时需要排序数组,找出整个数组的非负整数
但是需要注意当前删除的数字是不是小于上一次的最小非负整数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public static void main (String[] args) { Scanner in = new Scanner (System.in); int times = in.nextInt(); for (int i = 0 ; i < times; i++) { int len = in.nextInt(); int allNums = in.nextInt(); int one = in.nextInt(); int [] nums = new int [len]; for (int j = 0 ; j < len; j++) { nums[j] = in.nextInt(); } int [] tempNums = Arrays.copyOf(nums, len); Arrays.sort(tempNums); int mex = len; for (int j = 0 ; j < len; j++) { if (tempNums[j] != j) { mex = j; break ; } } int ans = mex * allNums; for (int j = 0 ; j < len; j++) { if (nums[j] < mex) { mex = nums[j]; } ans=Math.min(ans,((j+1 )*one)+(allNums*mex)); } System.out.println(ans); } }
小美的彩带 小美的彩带是由一条长度为 n 的彩带一直无限循环得到的,彩带的每一个位置都有一个颜色,用 ai 表示。因此当 i>n 时,ai = ai-n 。
小美每次会从左往后或从右往左剪一段长度为 x 的彩带,她想知道她每次剪下来的彩带有多少种颜色。
输入描述
第一行输入两个整数 n,q(1<=n,q<=2*10^5) 代表彩带长度、剪彩带次数。
第二行输入 n 个整数 a1,a2,…,an(1<=ai<=10^9) 代表彩带每一个位置的颜色。
此后 q 行,每行输入一个字符 c 和一个整数 x(1<=x<=10^9; c∈L,R
) 代表裁剪方向和裁剪长度,其中 ‘L’ 说明从左往右剪, ‘R’ 说明从右往左剪 。
输出描述
对于每一次裁剪彩带,在一行上输出一个整数代表颜色数量。
示例 1
输入
1 2 3 4 5 6 3 1 1 4 5 1 4 L 2 L 3 R 12
输出
说明
1 2 3 第一次剪彩带,剪下来的是 [1,1] ,有 {1} 这 1 种颜色; 第二次剪彩带,剪下来的是 [4,5,1] ,有 {1,4,5} 这 3 种颜色; 第三次剪彩带,剪下来的是 [1,1,4,5,1,4,1,1,4,5,1,4] ,有 {1,4,5} 这 3 种颜色。
思路与代码
离散化+离线处理(莫队)+树状数组。
观察数据范围:n<=200000, q<=200000,那么此题大概率是一个O(q*logn)的复杂度的要求。
由于是循环数组,那么假设我们区间的长度x是大于n的,那么必然是所有的元素数量的个数。我们不妨可以进行以下处理:将所有的区间先读取进内存,然后将区间排序,做一个预处理,这样对于不同的区间运算可以加速,避免重复运算。(思想就是莫队算法)
由于a[i]的范围是10^9,因此需要对数据进行离散化处理,最后我们使用树状数组来快速计算区间内的不同颜色数量,这个复杂度可以做到O(nlogn)。
类似题目思路可以参考洛谷:https://www.luogu.com.cn/problem/P1972
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 import java.util.*;public class Main { private static final int MAXN = 2000007 ; private static long [] a = new long [MAXN]; private static long [] ans = new long [MAXN]; private static long [] hsh = new long [MAXN]; private static long [] vis = new long [MAXN]; private static long [] c = new long [MAXN]; private static class Query { int l, r, id; Query(int l, int r, int id) { this .l = l; this .r = r; this .id = id; } } private static int lowbit (int x) { return x & -x; } private static void update (int x, int k) { for (int i = x; i < MAXN; i += lowbit(i)) { c[i] += k; } } private static long getsum (int x) { long sum = 0 ; for (int i = x; i > 0 ; i -= lowbit(i)) { sum += c[i]; } return sum; } public static void main (String[] args) { Scanner scanner = new Scanner (System.in); int n = scanner.nextInt(); int q = scanner.nextInt(); List<Query> queries = new ArrayList <>(); for (int i = 1 ; i <= n; i++) { a[i] = scanner.nextLong(); hsh[i] = a[i]; } Arrays.sort(hsh, 1 , n + 1 ); int len = 1 ; for (int i = 2 ; i <= n; i++) { if (hsh[i] != hsh[len]) { hsh[++len] = hsh[i]; } } for (int i = 1 ; i <= n; i++) { a[i] = Arrays.binarySearch(hsh, 1 , len + 1 , a[i]) + 1 ; a[n + i] = a[i]; } int nl = 1 , nr = n * 2 ; for (int i = 1 ; i <= q; i++) { char c = scanner.next().charAt(0 ); int x = scanner.nextInt(); x = Math.min(x, n); if (c == 'L' ) { if (nl > n) { nl -= n; } queries.add(new Query (nl, nl + x - 1 , i)); nl += x; } else { if (nr <= n) { nr += n; } queries.add(new Query (nr - x + 1 , nr, i)); nr -= x; } } queries.sort(Comparator.comparingInt(a -> a.r)); int cur = 1 ; for (Query query : queries) { for (int i = cur; i <= query.r; i++) { if (vis[(int ) a[i]] > 0 ) { update((int ) vis[(int ) a[i]], -1 ); } update(i, 1 ); vis[(int ) a[i]] = i; } cur = query.r + 1 ; ans[query.id] = getsum(query.r) - getsum(query.l - 1 ); } for (int i = 1 ; i <= q; i++) { System.out.println(ans[i]); } scanner.close(); } }
答案版本2 :主席树
该问题本质为求数组内颜色个数, 如果L大于整个数组,就是数组本身的答案。
如果L < 整个数组,考虑分为一个前缀一个后缀进行求解。我们可以将两段数组拼在一起。然后问题变为多次求一个区间内的数的种类。由于n = 1e5 ,无法使用朴素暴力获得满分。rmq询问颜色个数,经典主席树,也就是对颜色做可持久化值域线段树,然后差分询问两个历史版本即可。由于太过复杂,这里不放代码。
考虑n = 1e3的情况拿部分分,我们可以使用一个set,每次截取数组子段,装入set进行查询他的size。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def main (): n, q = map (int , input ().split()) a = list (map (int , input ().split())) unique_elements = set (a) curl = 0 curr = n - 1 for _ in range (q): op, x = input ().split() x = int (x) if x > n: print (len (unique_elements)) elif op == "L" : tmp = set () for _ in range (x): tmp.add(a[curl]) curl = (curl + 1 ) % n print (len (tmp)) else : tmp = set () for _ in range (x): tmp.add(a[curr]) curr = (curr - 1 + n) % n print (len (tmp))if __name__ == "__main__" : main()
总结 第一道题想出来了,比较简单。
第二道题也有了思路,但是卡在了如何找出没出现的最小非负整数+遍历时动态更新。
原本思路是原地哈希,因为在leetcode上做过类似的题,这样的时间复杂度比较小。
但还是答案直接,直接排序,找出没出现的。
第三题不会,放弃,不是给我做的
收货:
每道题的题解都得看看有没有别的题解,也许解法不是最优,但是思路是值得学习的。
这次学会了看数据范围,有些是暴力不了的,如第三题,那就直接放弃吧,也许暴力能偷一点点?