shopee2019编程笔试题
1.shopee的办公室
链接:https://www.nowcoder.com/questionTerminal/a71f3bd890734201986cd1e171807d30?answerType=1&f=discussion
来源:牛客网
输入描述:
第一行 x,y,n (0<x<=30, 0<y<=30, 0<=n<= 20) 表示x,y小虾的座位坐标,n 表示boss的数量( n <= 20)
接下来有n行, 表示boss们的坐标(0<xi<= x, 0<yi<=y,不会和小虾位置重合)
x1, y1
x2, y2
……
xn, yn
输出描述:
输出小虾有多少种走法
输入
3 3 2 1 1 2 2
输出
4
解析
维护一个动态规划表,当自己位置在(i,j)时,走法一共有boss[i][j]=boss[i-1][j]+boss[i][j-1]种,因为走法只有向右或者向上,而且只差一步到达(i,j),因此情况相加即可。
需要注意如果小虾和厕所在同一列/同一行,那么走法只有一种,即一直向上走/一直向右走
解答
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); //x,y表示小虾的座位坐标 int x = scanner.nextInt(); int y = scanner.nextInt(); //n表示boss的数量 int n = scanner.nextInt(); long[][] boss = new long[x+1][y+1]; //接下来有n行, 表示boss们的坐标.-1表示不可走 for(int i=0; i<n; i++) { int xn = scanner.nextInt(); int yn = scanner.nextInt(); boss[xn][yn] = -1; } //在同一列,只有一种情况,只能右移 for(int i=0; i<=x; i++) { boss[i][0] = 1; } //在同一行,只能上移 for(int j=0; j<=y; j++) { boss[0][j] = 1; } for(int i=1; i<=x; i++) { for(int j=1; j<=y; j++) { //如果该位置有boss,那么达到的方案数为0 if (boss[i][j] == -1) { boss[i][j] = 0; } //可以向上/向右 //当自己位置在(i,j)时,走法一共有boss[i][j]=boss[i][j-1]+boss[i-1][j],因为这两种情况都是只差一步就到(i,j) else { boss[i][j] = boss[i][j-1]+boss[i-1][j]; } } } System.out.println(boss[x][y]); }
2.shopee的零食柜
链接:https://www.nowcoder.com/questionTerminal/24a1bb82b3784f86babec24e4a5c93e0?answerType=1&f=discussion
来源:牛客网
shopee的零食柜,有着各式各样的零食,但是因为贪吃,小虾同学体重日益增加,终于被人叫为小胖了,他终于下定决心减肥了,他决定每天晚上去操场跑两圈,但是跑步太累人了,他想转移注意力,忘记痛苦,正在听着音乐的他,突然有个想法,他想跟着音乐的节奏来跑步,音乐有7种音符,对应的是1到7,那么他对应的步长就可以是1-7分米,这样的话他就可以转移注意力了,但是他想保持自己跑步的速度,在规定时间m分钟跑完。为了避免被累死,他需要规划他每分钟需要跑过的音符,这些音符的步长总和要尽量小。下面是小虾同学听的歌曲的音符,以及规定的时间,你能告诉他每分钟他应该跑多少步长?
输入描述:
输入的第一行输入 n(1 ≤ n ≤ 1000000,表示音符数),m(1<=m< 1000000, m <= n)组成,
第二行有 n 个数,表示每个音符(1<= f <= 7)
输出描述:
输出每分钟应该跑的步长
输入
8 5 6 5 6 7 6 6 3 1
输出
11
解析
这个题目,题意比较难理解。因为要求在m分钟内跑完,那么规划每一分钟跑的步长。其实就是把n个音符,划分成为m组,使得每一组的和的最大值尽可能小(“规划他每分钟需要跑过的音符,这些音符的步长总和要尽量小”)。
因此可以类似于leetcode的410题“分割数组的最大值”,通过二分法来做。
1.当m=1时,即将所有的数组看成一个整体,也就是一分钟跑完,最小的最大值就是音符的步长总和;
2.当m=n时,即将每一个数看成一个子数组,每一分钟跑一个音符的步长,此时最小的最大值 也就是 所有音符步长的最大值
3.因此m的范围为1<=m<=n,每分钟跑的步长的范围是[max(每一个音符),sum(每一个音符)]
4.利用二分查找,找到符合m的最大值的最小的结果。以中位数作为一个子数组的最大容量 的初始值
5.假设新开辟一个子数组来存储音符,count=1;利用贪心思想,按照顺序将元素依次放入,因为要相邻。直到加入下一个元素时,当前子数组里的音符步长和>中位数。
6.那么可以发现一个中位数容量的子数组无法容纳整个数组元素,需要再扩充一个数组。count++
7.再将剩余元素放入另一个数组.......如果放不下,就继续扩充
8.最终得到count,如果count>m,说明划分的子数组大于预期的时长,有太多的子数组,也就是一个子数组的容量太小了,那么就要扩大容量。原来的容量是中位数,那么现在应该left=mid+1,将整个部分挪到线段轴的右边。反之说明子数组太少,需要减少容量,将整个部分挪到线段轴的左侧,right=mid-1
9.直到left和right重合
解答
public static void main(String[] args) { Scanner scan = new Scanner(System.in); //音符数 long n = scan.nextLong(); //规定时间m分钟跑完 long m = scan.nextLong(); int[] musics = new int[(int) n]; long left = 0, right = 0; //有 n个数,表示每个音符 for(int i=0; i<n; i++) { musics[i] = scan.nextInt(); left = Math.max(left, musics[i]); right += musics[i]; } while(left<right) { int count = 1; long mid = (left+right)>>>1; int sum = 0; for(int music: musics) { if (sum+music > mid) { sum = 0; count++; } sum += music; } //划分太多 if (count>m) { left = mid+1; } //划分太少 else { right = mid; } } System.out.println(left); }
番外:分割数组的最大值(leetcode410)
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。 注意: 数组长度 n 满足以下条件: 1 ≤ n ≤ 1000 1 ≤ m ≤ min(50, n) 示例: 输入: nums = [7,2,5,10,8] m = 2 输出: 18 解释: 一共有四种方法将nums分割为2个子数组。 其中最好的方式是将其分为[7,2,5] 和 [10,8], 因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/split-array-largest-sum 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解答
class Solution { public int splitArray(int[] nums, int m) { int left = 0; int right = 0; for(int num: nums){ //当每个元素作为一个子数组,那么最小的最大值 也就是所有元素的最大值 left = Math.max(left, num); //当整个数组作为一部分,最小的最大值 也就是所有元素的和 right += num; } //利用二分法查找,找到符合m的最大值的最小的结果 while(left<right){ //刚开辟的用来存储的子数组个数 int count = 1; int mid = (left+right)>>>1; int sum = 0; //由于要连续数字,因此将数组元素按顺序逐个往里放 for(int num: nums){ //直到下一个元素放不下了,就开辟一个新的数组 if(num+sum > mid){ sum = 0; count++; } sum += num; } //表示划分出太多的子数组,即数组的容量太少,mid应该加大,需要扩大容量 if(count > m){ left = mid+1; } //表示划分出太少的子数组,即数组的容量太大,需要减少容量 else{ right = mid; } } return left; } }
3.实现字通配符*
链接:https://www.nowcoder.com/questionTerminal/bab19e5b95b54744aa824e0d7be51487
来源:牛客网
输入描述:
第一行输入通配字符串
第二行输入要匹配查找的字符串
输出描述:
输出所有匹配的字串起始位置和长度,每行一个匹配输出
如果不匹配,则输出 -1 0
如果有多个按照起始位置和长度的正序输出。
输入
shopee*.com shopeemobile.com
输出
0 16
解析
时刻注意检查数组是否越界(left<right,当left>=right,说明目标串已经匹配到字符串末尾)
类似于剑指offer的正则表达式匹配
1.同时匹配到末尾,匹配成功
2.模式串到末尾,字符串没有到末尾,匹配失败
3.字符串到末尾,模式串没到末尾,且模式串后一位不为*,匹配失败
4.模式串后一位为*
1)匹配到最后一个字符,pattern为*,那么将pattern看成空字串。pattern向后移一位
2)匹配过程中,可以将*看成空字串,pattern向后移一位/将*看成多个字符,target向后移一位
5.当前模式串位置不是*,且匹配成功。pattern和target都向后移一位
6.当前模式串不是*,且匹配失败。匹配失败
7.其余情况,匹配失败。
解答
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); //通配字符串 String pattern = scanner.nextLine(); //要匹配查找的字符串 String target = scanner.nextLine(); char[] patternChar = pattern.toCharArray(); char[] targetChar = target.toCharArray(); int count = 0; for(int i=0; i<targetChar.length; i++) { for(int j=i; j<targetChar.length; j++) { if(match(patternChar, targetChar, 0, i, j)) { System.out.println(i+" "+(j-i+1)); count++; } } } if (count == 0) { System.out.println("-1 0"); } } //匹配target[left,...,right],匹配到模式串的第i位 private static boolean match(char[] pattern, char[] target, int i, int left, int right) { // TODO Auto-generated method stub //刚好匹配完 if (i==pattern.length && left>right) { return true; } //target已经匹配完,但是pattern还没到结尾,且pattern的后一位还不是*,没法看成空字符串 if (left>right && i!=pattern.length && pattern[i]!='*') { return false; } //已经到了模式串的最后一位,但是target还没匹配完 if (i==pattern.length && left<=right) { return false; } //当通配字符是*时,通配字符向后移动/查找字符向后移动 else if (pattern[i]=='*') { //匹配到最后一个字符,pattern后面还有*,那么将*看成空字符,pattern往后移一位匹配 if (left>right) { return match(pattern, target, i+1, left, right); } //匹配过程中遇到*,那么如果*看成空字串,则pattern后移一位
//如果*看成多个字符,首先*与当前字符匹配,然后就开始与target的下一个字符匹配,那么target向后移一位
else { return match(pattern, target, i, left+1, right) || match(pattern, target, i+1, left, right); } } //当前模式串是字符,且与目标串匹配,则后移匹配下一字符 else if(left<=right && pattern[i]==target[left]){ return match(pattern, target, i+1, left+1, right); } //匹配中出现了不同的字符,匹配失败 else if(left<=right && pattern[i]!=target[left]){ return false; } else { return false; } }
4.建物流中转站
链接:https://www.nowcoder.com/questionTerminal/c82efaf9e2cc42cda0a8ad795845eceb?answerType=1&f=discussion
来源:牛客网
Shopee物流会有很多个中转站。在选址的过程中,会选择离用户最近的地方建一个物流中转站。
假设给你一个二维平面网格,每个格子是房子则为1,或者是空地则为0。找到一个空地修建一个物流中转站,使得这个物流中转站到所有的房子的距离之和最小。 能修建,则返回最小的距离和。如果无法修建,则返回 -1。
若范围限制在100*100以内的网格,如何计算出最小的距离和?
当平面网格非常大的情况下,如何避免不必要的计算?
输入描述:
4
0 1 1 0
1 1 0 1
0 0 1 0
0 0 0 0
先输入方阵阶数,然后逐行输入房子和空地的数据,以空格分隔。
输出描述:
8
能修建,则返回最小的距离和。如果无法修建,则返回 -1。
输入
4 0 1 1 0 1 1 0 1 0 0 1 0 0 0 0 0
输出
8
解析
因为范围限定100*100,所以其实可以直接暴力了。
对于每一个空地,去遍历其与房子的距离。找到最短的距离
解答
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); //方阵阶数 int n = scanner.nextInt(); //逐行输入房子和空地的数据,每个格子是房子则为1,或者是空地则为0 int[][] matrix = new int[n][n]; ArrayList<int[]> house = new ArrayList<>(); for(int i=0; i<n; i++) { for(int j=0; j<n; j++) { matrix[i][j] = scanner.nextInt(); if (matrix[i][j]==1) { int[] xy = new int[2]; xy[0] = i; xy[1] = j; house.add(xy); } } } int ret = Integer.MAX_VALUE; //找到一个空地修建一个物流中转站,使得这个物流中转站到所有的房子的距离之和最小。 for(int i=0; i<n; i++) { for(int j=0; j<n; j++) { int temp = 0; //遍历每一个空地,看其到所有房子的距离 if (matrix[i][j]==0) { for(int k=0; k<house.size(); k++) { temp+=Math.abs(house.get(k)[0]-i)+ Math.abs(house.get(k)[1]-j); } //ret存储当前房子距离最小的可能性。如果temp小于则替换 ret = ret>temp?temp:ret; } } } //能修建,则返回最小的距离和。如果无法修建,则返回 -1 System.out.println(ret==Integer.MAX_VALUE?-1:ret); }