【2021】阿里巴巴编程题(4星)
https://www.nowcoder.com/test/30440638/summary
1、二维最长上升子序列
小强现在有n个物品,每个物品有两种属性Xi和Yi,他想要从中挑出尽可能多的物品满足以下条件:对于任意两个物品,满足 X 和 Y 同升同降
问最多能挑出多少物品。
思路:
-
先将所有物品按x升序排列,随后在无序的y中取一个最长上升子序列即可。
-
需要注意的是,在排序过程中,对于相同大小的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
思路:
- 将a/b提取最大公约数,化简为a/b,辗转相除法
- x=a * unit<A; y=b * unit<B
- 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;
}
}