【牛客】【刷题节】美团2024届秋招笔试第二场编程真题

1.小美的加法【简单题】

题意理解

        给定一个数组做连加操作,其中只能将一个加号变成乘号

        将哪个加号变成乘号,使式子最后的结果最大

解题思路

        只有将两个相邻且乘机最大的数之间变成乘号后,才能保证整个式子结果最大

        所以第一步找到这两个数的位置

        在所有元素和中减去这两个元素,加上这两个元素的乘积即可。

        这里采用滑动窗口来找相邻乘积最大的两个元素,其中滑动窗口大小为2.

注意:数组的元素顺序不能变,所以不能排序

1.滑动窗口解题

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int len=in.nextInt();
        long[] nums=new long[len];
        for(int i=0;i<len;i++) nums[i]=in.nextLong();
        Main main=new Main();
        System.out.println(main.compuate(nums));
    }

    public Long compuate(long[] nums){
        long result=0;
        int maxNeighbor=0;
        for(int i=0;i<nums.length-1;i++){
            result+=nums[i];
            if(nums[i]*nums[i+1]>nums[maxNeighbor]*nums[maxNeighbor+1]){
                maxNeighbor=i;
            }
        }
        result+=nums[nums.length-1];
        result=result-nums[maxNeighbor]-nums[maxNeighbor+1]+nums[maxNeighbor]*nums[maxNeighbor+1];
        return result;
    }
}

2.复杂度分析

时间复杂度:O(n) 遍历数组的时间耗费

空间复杂度:O(n) 数组存储的空间耗费

2.小美的数组操作【技巧】

题意理解

        给定一个数组,每次选中两个数,一个元素+1,一个元素-1

        最终使数组中的众数出现的次数最多。

解题思路

        我的思路:求平均数,然后将数组排序,让所有的数都向平均数靠近。

        对于平均数为整数的来说,可以实现。但对于平均数不为整数的发生错误。

        借鉴大佬的思路:

        

个人理解

        对于平均数为整数的,所有数向平均数靠近。

        对于平均数部位整数的,去掉一个元素,保证其他元素的平均数是整数,此时重复上述操作。 关键:如何挑选去除的那个元素呢,即如何选取垃圾数。 

        贪心思路:选取最大值或最小值为垃圾数,将多余的位数分给垃圾数——将avg+1和avg-1分别作为众数来计算,进行比较。

注意:+1和-1操作是对称的,有k个+1则有k个-1操作

        对于[2,2,4,4]来说,不操作,2/4都是众数,都有两个。但是不满组足众数最多。

        操作后:[3,3,3,3],众数是3有4个

1.贪心解题

import java.util.Scanner;
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int len = in.nextInt();
        long[] nums = new long[len];
        for (int i = 0; i < len; i++) {
            nums[i] = in.nextLong();
        }
        Main main = new Main();
        System.out.println(main.compute(nums));
    }
    public long compute(long[] nums){
        long count=0;
        int len=nums.length;
        Arrays.sort(nums);
        long sum=0;
        for(int i=0;i<len;i++){
            sum+=nums[i];
        }
        if(sum%len==0){
            //没有垃圾数
            count=countK(nums,sum/len,0,len-1)/2;
        }else{
            //选最大的数为垃圾数
            long avg=(sum-nums[len-1])/(len-1);
            //丢垃圾
            long countMA1=countK(nums,avg,1,len-2)+Math.abs(avg-nums[0])+Math.abs((sum-nums[len-1]-avg*(len-1)));//中间得操作+第一个数取+垃圾桶丢
            long countMA2=countK(nums,avg+1,1,len-2)+Math.abs(avg+1-nums[0])+Math.abs((sum-nums[len-1]-(avg+1)*(len-1)));//中间操作+第一个数取+垃圾桶拿

            //选最小的数为垃圾数
            avg=(sum-nums[0])/(len-1);
            long countMI1=countK(nums,avg,1,len-2)+Math.abs(avg-nums[len-1])+Math.abs((sum-nums[0]-avg*(len-1)));//中间操作数+最后一个数丢+垃圾桶丢
            long countMI2=countK(nums,avg+1,1,len-2)+Math.abs(avg+1-nums[len-1])+Math.abs((sum-nums[0]-(avg+1)*(len-1)));//中间操作数+最后一个数丢+垃圾桶拿
            count=Math.min(Math.min(countMI1,countMI2),Math.min(countMA1,countMA2))/2;
        }
        return count;
    }

    public long countK(long[] nums,long avg,int left,int right){
        long count=0;
        for(int i=left;i<=right;i++){
            if(nums[i]<avg){
                count+=avg-nums[i];
            }
            if(nums[i]>avg){
                count+=nums[i]-avg;
            }
        }
        return count;
    }
}

代码里有一个技巧

2.复杂度分析

时间复杂度:O(n*log(n)) 排序的时间复杂度

空间复杂度:O(n) 排序所需的额外空间

主要是Arrays.sort的损耗

3.小美的01串翻转【技巧】-动态规划

题意理解:

        这道题理解起来有点绕:首先明确:  一个01串的权重:使得相邻位不同的最小操作数

        其次:统计所有子串(包含其本身、长度>=2的)权重的和。即为所求的值。

解题思路:

不成熟且错误的思路,故借鉴大佬思路

        

个人理解:       

        操作字符串的目的:为了使字符串相邻字符不相同

        那么:长度为s.len 的满足条件的子串有两种,要么以1开头,要么以0开头

        即:s=10001   那么满足条件的修改为: 10101  或01010

        将其子串与这两个目标串进行比较,同一位置值不同则说明需要修改,统计不同的字符,即为修改次数。

        两个修改次数取最小值,即为所求。

        上表中的每个位置[i,j]表示从i到位置j的子串的权值是多少

重新发现思路:

        这道题目可以用动态规划来解决。其中如何和动态规划结合起来呢。

        首先是子串,子串有一个开始位置和起始位置,其中每个位置有两种可能0或1

        (1)定义三维dp数组

        dp[i][j][0]表示从i开始到j位置的子串,若以0结尾的权值

        dp[i][j][1]表示从i开始到j位置的子串,若以1结尾的权值

        (2)递推公式

        dp[i][j][x] 表示从i到j位置得子串,以x结尾的权值
        //子串当前以0结尾
        dp[i][j][0]=dp[i][j-1][1];
        dp[i][j][1]=dp[i][j-1][0]+1;
        //当前以1结尾
        dp[i][j][0]=dp[i][j-1][1]+1;
        dp[i][j][1]=dp[i][j-1][0];

        (3)初始化:

        则i,j的权值=min(dp[i][j][0],dp[i][j][1])
        初始化: 对于dp[i][j]的值总是和dp[i][j-1]有关,所以从左到右填数
        dp[i][i]=0

1.解题

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String num = in.nextLine();
        Main main = new Main();
        System.out.println(main.compute(num));
    }
    public int compute(String num){
        /**
         * dp[i][j][x] 表示从i到j位置得子串,以x结尾的权值
         * //当前以0结尾
         * dp[i][j][0]=dp[i][j-1][1];
         * dp[i][j][1]=dp[i][j-1][0]+1;
         * //当前以1结尾
         * dp[i][j][0]=dp[i][j-1][1]+1;
         * dp[i][j][1]=dp[i][j-1][0];
         *                 }
         * 则i,j的权值=min(dp[i][j][0],dp[i][j][1])
         * 初始化: 对于dp[i][j]的值总是和dp[i][j-1]有关,所以从左到右填数
         * dp[i][i]=0
         */
        int sum=0;
        int len=num.length();
        int[][][] dp=new int[len][len][2];
        for(int i=0;i<len;i++){
            if(num.charAt(i)=='0'){
                dp[i][i][0]=0;
                dp[i][i][1]=1;
            }else{
                dp[i][i][0]=1;
                dp[i][i][1]=0;
            }
        }
        //遍历所有的子串
        for(int i=0;i<len-1;i++){
            for(int j=i+1;j<len;j++){
                if(num.charAt(j)=='0'){//当前以0结尾
                    dp[i][j][0]=dp[i][j-1][1];
                    dp[i][j][1]=dp[i][j-1][0]+1;
                }else{//当前以1结尾
                    dp[i][j][0]=dp[i][j-1][1]+1;
                    dp[i][j][1]=dp[i][j-1][0];
                }
                sum+=Math.min(dp[i][j][0],dp[i][j][1]);
            }
        }

        return sum;
    }
}

2.复杂度分析

时间复杂度:O(n^2)  遍历所有子串(开始和截止位置)

空间复杂度:O(n^3) 记录结果值得损耗 

4.小美的外卖订单编号【简单题】

题意理解

        订单号:[1,m],超过m时,重新从1开始计数

        题目的问题是:第q个订单的订单编号x是多少?

解题思路

        编号1到m,很容易想到取余的操作。

        一个数对m取余获得[0,m-1]的区域

        第1单: 1%m==1

        第2单: 2%m==2

              ……

        第m单:m%m==0,此时单号应该是m,而不是0

        除第m单特殊之外,其余单没有什么不同。         

1.解题

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
     public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int len=in.nextInt();
        int[][] nums=new int[len][2];
        for(int i=0;i<len;i++){
            nums[i][0]=in.nextInt();
            nums[i][1]=in.nextInt();
        }
        Main main=new Main();
        int[] result= main.compute(nums);
        for(int i=0;i<len;i++){
            System.out.println(result[i]);
        }
    }
    public int[] compute(int[][] nums){
        int[] result=new int[nums.length];
        for(int i=0;i<nums.length;i++){
            if(nums[i][1]%nums[i][0]==0){
                result[i]=nums[i][0];
            }else{
                result[i]=nums[i][1]%nums[i][0];
            }
        }
        return result;
    }
}

2.复杂度分析

时间复杂度:O(n) 遍历的时间损耗

空间复杂度:O(n) 保存结果的时间损耗 

5.小美的数组操作2【简单题】

题意理解

        根据选择的数,做出对应操作,最后判断数组是否是递增序列。

        是输出Yes,否则输出No

解题思路

        根据输入,做指定操作,

        遍历检查数组是否是递增的

1.解题

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int t=in.nextInt();
        Main main=new Main();
        String[] result=new String[t];
        for(int i=0;i<t;i++){//t次询问
            int n=in.nextInt();//数组长度
            int k=in.nextInt();//k次操作
            int[] nums=new int[n];
            int[][] kOpt=new int[k][2];
            for(int j=0;j<n;j++) nums[j]=in.nextInt();
            for(int j=0;j<k;j++){
                kOpt[j][0]=in.nextInt();
                kOpt[j][1]=in.nextInt();
            }
            result[i]=main.compute(nums,k,kOpt);
        }
        for(int i=0;i<t;i++){
            System.out.println(result[i]);
        }
    }

    public String compute(int[] nums,int k,int[][] kOpt){
        for(int i=0;i<k;i++){
            int u=kOpt[i][0]-1;
            int v=kOpt[i][1]-1;
            nums[u]++;
            nums[v]--;

        }
        for(int i=1;i<nums.length;i++){
            if(nums[i]<nums[i-1]) return "No";
        }
        return "Yes";
    }
}

2.复杂度分析

时间复杂度:O(n) 遍历数组进行检查的时间损耗

空间复杂度:O(n) 保存结果的空间损耗 

6.小美的数组构造【较难】-动态规划

题意理解

        数组a和数组b对应位置数不同,但a和b元素和相等。

        构造符合条件的正整数b有多少种方式。

解题思路

        没思路,这道题。

        结果这道题是一个动态规划类的题目。

        我去看了下别人的思路,然后猛的一看,不懂。纯看代码分析思路太难受了。啊啊啊啊

理解了,分析一下:

        这道题目还是一个动态规划的题,里面涉及状态转移的思路。

        以1 1 1数序列来演示
        dp[i][j]:表示sum=i,长度为j的串的可能的构造次数。
        则有:dp.size=(sum+1,len+1)=(4,4)

        其中特别的要求:b的元素是正整数,所以b的元素>=1
        根据题意:dp[0][j] 和dp[i][0]是没有意义的,全部初始化为0

       dp[j][j](j>=1)可以构造出全1的数列,若对应位置 a[j-1]不等于1,则该位置上可以取1,一种构造方式,否则为0.【题目要求对应位置元素不一致】

        当计算dp[3][3]时,我们可以将3分解为:(1,2)(2,1)两种方式

        当第3位取2时,前面的数有dp[1][2]种方式(nums[3-1]=1,此位置可以取2)

        当第2位取1时,前面的数有dp[2][2]种方式(nums[3-1]=1,此位置不可以取2)——不符合要求

        即dp[3][3]=dp[1][2]=0

        具体步骤来说:

        (1)首先定义二维dp数组,其中size 为[sum+1][len+1]

        dp[i][j]表示和为i,长度为j的,符合条件的构造方式有几种。

        (2)递推公式

       dp[i][j]首先判断,将sum=i分解为两部分,有多少种排列

        如:sum=3   则有(1,2)、(2,1)两种方式

        sum=i时,第j位的取值范围是[1,j-1]

        其中第j位的值==a[j-1]时,j值不能取,应为会和a的j-1位置重复,不符合要求

        当第j位取k值时,sum=i-k,长度=j-1有dp[i-k][j-1]种构造方法

        遍历k值,若第j位能取k,则dp[i][j]+=dpdp[i-k][j-1]

         (3) 初始化

       dp[0][j] 和dp[i][0]是没有意义的,全部初始化为0

       dp[j][j](j>=1)可以构造出全1的数列,若对应位置 a[j-1]不等于1,则该位置上可以取1,一种构造方式,否则为0.【题目要求对应位置元素不一致】

特别的:

        这道题目计算的数值会很大,所以元素及需要求和的值定义为long

        同时某个元素也可能会很大,所以在做加时,及时对元素做取模操作(%(7+10^9))

这道题目对我来说算是这7个里面最难的了,理解题想了半天,然后看明白了,不知道咋动手,想到应该用动态规划做,有没有思路了。想到了dp[sum][len],搞明白意义,理清楚之后,突然豁然开朗,也不是很难了,好像。但是搞明白还是花了很长时间。

1.解题

import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int len = scanner.nextInt();
        long[] arrA = new long[len];
        for(int i=0;i<len;i++){
            arrA[i]=scanner.nextLong();
        }
        Main main=new Main();
        System.out.println(main.compute(arrA));

    }
public long compute(long[] nums ) {
        long sum = 0;
        int len = nums.length;
        for (int i = 0; i < nums.length; i++) sum += nums[i];
        long[][] dp = new long[(int) (sum + 1)][len + 1];//求dp[sum][0];
        for (int i = 1; i < sum + 1; i++) {
            Arrays.fill(dp[i], 0);
            if (nums[0] == i) {
                dp[i][1] = 0;
            } else {
                dp[i][1] = 1;
            }
        }
        for (long i = 2; i <= sum; i++) { //遍历和sum
            for (int j = 2; j <= len; j++) { //遍历长度len
                /**
                 * 计算dp[i][j]只需要固定第j个位置上的数字+dp[i-1][j-1]集合
                 * 其中位置j可以取的值:从1开始,到i-1,用k来遍历,为了不和原串a冲突
                 * 要判断j位置不可以取nums[j-1]的值
                 */
                long res=0;
                for (int k = 1; k <= i - 1; k++) {
                    if (nums[j - 1] == k) continue;
                    res= (long) ((res%(7+Math.pow(10,9)))+ (dp[(int)i - k][j - 1]%(7+Math.pow(10,9))));
                }
                dp[(int)i][j]=res;
            }

        }
        return dp[(int)sum][len];

    }
}

2.复杂度分析

时间复杂度:O(n^2) for循环时间损耗

空间复杂度:O(n^2) dp数组空间损耗

7.美团商家注册系统【简单题】

题意理解:

 这道题不难,考验数据的一个处理过程。

 其中我们需要统计的信息包含: 店铺名,主店地址,分店地址

 其中一些操作包含:

          只包含小写字母的店铺名;

          存在的店铺,比较地址是否和主店一致:

                和主店一致:注册失败

                和主店不一致:注册为分店

          注册为分店时:店铺名一致,地址一致,注册失败

          按店铺名升序输出

解题思路

        使用两个map来对数据进行组织

        businessMap维护店铺名和主店地址

        businessInfoMap维护店铺名,分店地址Set

        Set将分店地址去重,即可获得分店数量。

1.解题

import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    Map<String,String> businessMap=new HashMap<>();
    Map<String, Set<String>> businessInfoMap=new HashMap<>();
    public static void main(String[] args) {
        Main main=new Main();
        Scanner in = new Scanner(System.in);
        int n=in.nextInt();
        in.nextLine();
        for(int i=0;i<n;i++){
            String business=in.nextLine();
            main.compute(business.split(" "));
        }
        String[] names=new String[main.businessMap.size()];
        int index=0;
        for(String name:main.businessMap.keySet()){
            names[index++]=name;
        }
        Arrays.sort(names);
        for(String name:names){
            System.out.println(name+" "+main.businessMap.get(name)+" "+main.businessInfoMap.get(name).size());
        }

    }
    public void compute(String[] businessInfo){
        //只能是小写字母
        char[] letter=businessInfo[0].toCharArray();
        for(int i=0;i<letter.length;i++){
            if(letter[i]>'z'||letter[i]<'a') return;
        }
        //注册
        if(!businessMap.containsKey(businessInfo[0])){//未注册过
            //注册
            businessMap.put(businessInfo[0],businessInfo[1]);
            businessInfoMap.put(businessInfo[0],new HashSet<>());
        }else{
            //已经注册过:是分店吗?
            if(!businessMap.get(businessInfo[0]).equals(businessInfo[1])){//是分店
                businessInfoMap.get(businessInfo[0]).add(businessInfo[1]);
            }
        }
    }
}

代码有个能改进的地方:关于按照餐厅名升序输出

List<String> keys=new ArrayList<>(businessMap.keySet())

Collections.sort(keys);

2.时间复杂度分析

时间复杂度:O(n*logn)主要耗费在店铺排序

空间复杂度:O(n)数据保存得空间耗费

 总结

题目见的得少,结果看到题目的时候,看了半天没理解题目的意思

对于int long的范围要有概念,否则会导致结果出错

看懂题目后,大部分其实是简单题,一些难一点的题目是动态规划问题。

关键点在于如何和动态规划思路联合起来,说起来还是题目做的少了。

  • 34
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值