shopee2019编程笔试题

1.shopee的办公室

链接:https://www.nowcoder.com/questionTerminal/a71f3bd890734201986cd1e171807d30?answerType=1&f=discussion
来源:牛客网
shopee的办公室非常大,小虾同学的位置坐落在右上角,而大门却在左下角,可以把所有位置抽象为一个网格(门口的坐标为0,0),小虾同学很聪明,每次只向上,或者向右走,因为这样最容易接近目的地,但是小虾同学不想让自己的boss们看到自己经常在他们面前出没,或者迟到被发现。他决定研究一下如果他不通过boss们的位置,他可以有多少种走法? 
输入描述:
第一行 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
输出描述:
输出小虾有多少种走法
示例1

输入

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)
输出描述:
输出每分钟应该跑的步长
示例1

输入

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
来源:牛客网
在Linux Shell命令下通配符'*'表示0个或多个字符, 现编写一段代码实现通配符'*'的功能,注意只需要实现'*', 不用实现其他通配符。 
输入描述:
第一行输入通配字符串
第二行输入要匹配查找的字符串
输出描述:
输出所有匹配的字串起始位置和长度,每行一个匹配输出
如果不匹配,则输出 -1 0
如果有多个按照起始位置和长度的正序输出。
示例1

输入

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。
示例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);
    }
    

 

posted @ 2020-08-30 22:46  闲不住的小李  阅读(1158)  评论(0编辑  收藏  举报