【牛客】阿里笔试练习

这篇博客介绍了四个编程题目,涉及二维最长上升子序列、幂次运算、不同二叉树个数和对称飞行器问题。解题思路包括动态规划、二分查找和位运算优化,同时提供了Java实现的代码示例。这些题目锻炼了程序员在算法和数据结构上的能力。
摘要由CSDN通过智能技术生成

【2021】阿里巴巴编程题(4星)

https://www.nowcoder.com/test/30440638/summary

1、二维最长上升子序列

小强现在有n个物品,每个物品有两种属性Xi和Yi,他想要从中挑出尽可能多的物品满足以下条件:对于任意两个物品,满足 X 和 Y 同升同降
在这里插入图片描述
在这里插入图片描述
问最多能挑出多少物品。

思路:

  1. 先将所有物品按x升序排列,随后在无序的y中取一个最长上升子序列即可。

  2. 需要注意的是,在排序过程中,对于相同大小的x,需要将大的y排在前边,因为排在后边,因为x不递增,会导致错误的结果。例如(1,2) (1,3) (1,4),这一组中最长子序列长度正确应为1,但将小y排在前边,所有的 y序列 会算出3的错误结果。

参考300、最长上升子序列【中等】,有动态规划和二分两种解法

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

public class Main {
    //用于x,y同时排序
    public static class XY implements Comparable{
        public int x;
        public int y;
        public XY(int x,int y){
                this.x=x;
                this.y=y;
        }
        //将所有物品按【x】【升序】排列,x相同按【y】【降序】
        public int compareTo(Object o){
            XY tmp=(XY) o;
            if (this.x > tmp.x) return 1;
            else if (this.x < tmp.x) return -1;
            else {
                //x相同,y大的放前边
                if (this.y > tmp.y) return -1;
                else if (this.y < tmp.y)return 1;
                return 0;
            }
        }
    }
    public static void main(String[] args) {
        //最长上升子序列的变种,需要保证xy的同升同降,
        // 即先将所有物品按【x】升序排列,随后在无序的【y】中取一个最长上升子序列即可
        Scanner sc = new Scanner(System.in);
        int totalnum = sc.nextInt();//共几组数
        for (int k = 0; k < totalnum; k++){
            int len = sc.nextInt();//每组数几个数字
            int[]nums1=new int[len];
            int[]nums2=new int[len];
            for (int i = 0; i <len; i++) nums1[i]=sc.nextInt();
            for (int i = 0; i <len; i++) nums2[i]=sc.nextInt();
            XY[]xy=new XY[len];
            for(int i=0;i<len;i++) xy[i]=new XY(nums1[i],nums2[i]);
            Arrays.sort(xy);
            int res=help(xy);
            System.out.println(res);
        }
    }

    //最长上升子序列 二分
    public static int help(XY[] nums) {
        int len=nums.length;
        if(len<2) return len;
        List<Integer> list=new ArrayList<>();
        list.add(nums[0].y);
        for(int i=1;i<len;i++){
            if (nums[i].y>list.get(list.size()-1)){//1、nums[i]>list中的最大数
                list.add(nums[i].y);//直接加在list后面
                continue;
            }
            //2、二分查找插入位置,用nums[i]覆盖掉【比nums[i]大的元素中】最小的那个
            int l=0,r=list.size();
            while(l<r){
                int mid=(l+r)/2;
                if(list.get(mid)<nums[i].y) l=mid+1;
                else r=mid;
            }
            list.set(l,nums[i].y);
        }
        return list.size();
    }
}

2、n次方

小强发现当已知xy=B以及x+y=A时,能很轻易的算 xn+yn 出的值。但小强想请你在已知 A和B的情况下,计算出 xn+yn的值。因为这个结果可能很大,所以所有的运算都在模1e9+7下进行.


输入例子1:
                3
                4 4 3
                2 3 4
                5 2 6
输出例子1:
                16
                999999993
                9009

思路:
num[1]=x+y
num[2]=x2+y2=(x+y)2-2xy=a2-2b
num[3]=x3+y3=(x+y)(x2+y2)-xy(x+y)=num[1] * num[2] -b * num[1]
num[n]=a * num[n-1] -b * num[n-2]


public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //T表示有T组数据
        int T = sc.nextInt();
        for (int i = 1; i <= T; i++) {
            //n表示有n个物品
            int A = sc.nextInt();
            int B = sc.nextInt();
            int n = sc.nextInt();
            long result = helper(A, B, n);
            System.out.println(result);

        }
    }

    private static long helper(int a, int b, int n) {
        if (n == 0) return 2;
        if (n == 1) return a;
        // 未优化
        long[] dp = new long[n + 1];//dp[i]表示x的第i次方+y的第i次方的和
        dp[0] = 2;
        dp[1] = a;
        double mod = 1e9 + 7;
        for (int i = 2; i < dp.length; i++) {
            dp[i] = (long)(((a * dp[i - 1] % (mod)) - (b * dp[i - 2] % (mod)) + mod) % (mod));
        }
        return dp[n];
    }
}

优化空间

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //T表示有T组数据
        int T = sc.nextInt();
        for (int i = 1; i <= T; i++) {
            //n表示有n个物品
            int A = sc.nextInt();
            int B = sc.nextInt();
            int n = sc.nextInt();
            long result = helper(A, B, n);
            System.out.println(result);

        }
    }

    private static long helper(int a, int b, int n) {
        if (n == 0) return 2;
        if (n == 1) return a;
        // 优化,因为每次求结果,只与前一次和前前一次的结果有关
        // 所以dp只需保存长度为3的数组即可。
        long[] dp1 = new long[3];
        dp1[1] = 2;
        dp1[2] = a;
        double mod = 1e9 + 7;
        for (int i = 2; i <= n; i++) {
            dp1[0] = dp1[1];
            dp1[1] = dp1[2];
            dp1[2] = (long)(((a * dp1[1] % (mod)) - (b * dp1[0] % (mod)) + mod) % (mod));
        }

        return dp1[2];
    }
}

3、不同的二叉树的个数

小强现在有个节点,他想请你帮他计算出有多少种不同的二叉树,满足节点个数n为且树的高度不超m过的方案。因为答案很大,所以答案需要模上1e9+7后输出,
树的高度::定义为所有叶子到根路径上节点个数的最大值.
例如: 当n=3,m=3时,有如下5种方案:
在这里插入图片描述

思路:动态规划,参考96、不同的二叉搜索树【中等】
注意:dp要用long!!!

import java.util.Scanner;
import java.util.Arrays;
public class Main{
    public static void main(String[] args) {
        int kmod= (int) (1e9+7);
        Scanner in = new Scanner(System.in);
        int n=in.nextInt();
        int m=in.nextInt();
        //dp[n][m] 总数为n 高度最大为m
        long[][]dp=new long[n+1][m+1];
        Arrays.fill(dp[0], 1);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                for(int k=0;k<i;k++){//总共i,左边k,中间1,右边i-k-1
                    dp[i][j] += dp[k][j-1]*dp[i-k-1][j-1]%kmod;
                    dp[i][j] %=kmod;
                }
            }
        }
        System.out.println(dp[n][m]);
    }
}

4、对称飞行器(三维bfs)
三维广度搜索+位运算优化时间和空间
飞行器有次数限制。所以(x,y)点的状态应该再加一个维度:飞行次数z。P(x,y,z1)和Q(x,y,z2)是两种不同的状态。

假设z1<z2,即P和Q在相同的横纵坐标,但是P剩余的飞行次数更多。假设从(x,y,z2)走到终点的最优解是n步,从(x,y,z1)一定可以走n步到达终点。此时P状态走到最优点的步数更少。所以在层次遍历的最优解中不该包含z2.

也就是说在向z增加转移的过程中,我们应该看matrix[x][y][0]…matrix[x][y][z]中是否已经有为1的点,如果有就不必再在第z层再走x,y。

我们可以用位运算压缩三维数组,matrix[x][y]第z位代表是否已经遍历过(x,y,z)点。至于判断该点是否可走,我们应该看当前位置(x,y)小于等于z的位是否有1。类似于子网掩码的算法。高于z为置0,低位置1,与matrix[x][y]相与。得到结果大于0则说明有1不需遍历。等于0说明使用更少的飞行器没有走过这一点。可以加入遍历集。

【2021】阿里巴巴编程题(2星)

https://www.nowcoder.com/question/next?pid=30440590&qid=1664934&tid=52881336

2、选择物品(dfs)

有 n 个物品可供选择,必须选择其中 m 个物品,请按字典序顺序输出所有选取方案的物品编号

(1,2,3)与(3,2,1)等被认为是同一种方案,输出字典序最小的即可

输入描述:
对于每一组测试数据, 每行输入2个数 n 和 m。

输入例子1:
4 1

输出例子1:
1
2
3
4

import java.util.*;

public class Main {
    static List<Integer>path=new ArrayList<>();
    static List<List<Integer>>res=new ArrayList<>();
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n=scanner.nextInt();
        int m=scanner.nextInt();
        dfs(n,m,1);
        for(List<Integer>list:res){
            for(Integer num:list){
                System.out.print(num+" ");
            }
            System.out.println();
        }
    }
    public static void dfs(int n,int m,int depth){
        if(path.size()==m){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i=depth;i<=n;i++){
            path.add(i);
            dfs(n,m,i+1);
            path.remove(path.size()-1);
        }
    }
}

4、比例问题(最大公约数)

小强想要从[1,A]中选出一个整数x,从[1,B]中选出一个整数y。使得满足 x/y = a/b 的同时且,x和y的乘积最大。如果不存在这样的和,请输出“ 0 0”.

输入例子:
1000 500 3 1

输出例子:
999 333

思路:

  1. 将a/b提取最大公约数,化简为a/b,辗转相除法
  2. x=a * unit<A; y=b * unit<B
  3. unit取最大,即min(A/a,B/b)
import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int A=scanner.nextInt();
        int B=scanner.nextInt();
        int a=scanner.nextInt();
        int b=scanner.nextInt();

        int t=gcd(a,b);
        a/=t;
        b/=t;
        int unit=Math.min(A/a,B/b);
        System.out.println(unit*a+" "+unit*b);
    }
    public static int gcd(int a,int b){
        if(a<b){//保证a>b
            int t=a;
            a=b;
            b=t;
        }
        while(b>0){
            int temp=a%b;
            a=b;
            b=temp;
        }
        return a;
    }
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值