今天面试了一道题:
若有正整数a、b、c、d, 使 1/a + 1/b + 1/c + 1/d = 1, 且a <= b <= c <= d, 求a, b, c, d的可能组合.
分析:
1. ∵ a <= b <= c <= d
∴ 1/d<=1/c<=1/b<=1/a
∵ 1/a + 1/b + 1/c + 1/d = 1
∴ 1=1/a + 1/b + 1/c + 1/d<=4*1/a
∴ a<=4,且显然有a>=2,即2<=a<=4.
2. 分情况讨论:
若a=4,由a <= b <= c <= d的约束条件,显然只有一种情况 a=b=c=d=4;
若a=3,剩余可分配1-1/a=2/3,此时1/b>=(1-1/a)/3,即3=a<=b<=9/2,即b=3或4;
若b=3,剩余可分配1-1/a-1/b=1/3,此时1/c>=(1-1/a-1/b)/2,即 b<=c<=6
若c=3,1/a + 1/b + 1/c=1,不成立;
若c=4, 有成立序列[3, 3, 4, 12];
若c=5, d=2/15,不成立;
若c=6, 有成立序列[3, 3, 6, 6];
其余情况的分析同上。
3.问题:java浮点数运算存在精度问题。转为int型的判断条件。(笔试的时候没考虑。。)
i.最后的判断条件:1/a + 1/b + 1/c + 1/d=1 <==> a*b*c*d=b*c*d+a*c*d+a*b*d+a*b*c
ii.最小值:a<=b<=c<=d
iii.最大值:约束条件 (1).1/b>=(1-1/a)/3 <==> b(a-1)<=3a
(2).1/c>=(1-1/a-1/b)/2 <==> c(ab-a-b)<=2ab
(3).1/d>=(1-1/a-1/b-1/c)/1 <==> d(abc-ab-ac-bc)<=abc
iv.剩余未分配量的约束条件(>=0): (1).1-1/a>0 <==> a-1>0
(2).1-1/a-1/b>0 <==> ab-a-b>0
(3).1-1/a-1/b-1/c>0 <==> abc-ab-ac-bc>0
4.笔试时写法的正确版:4层for循环(笔试时未考虑浮点数运算的问题,凉凉。。)
/**
* 若有 1/a+1/b+1/c+1/d=1.且a<=b<=c<=d,求a/b/c/d的可能组合
* @return
*/
public List<int[]> getPlusOne(){
List<int[]> list=new ArrayList<>();
for(int a=2;a<=3;a++) { //因为已经知道a=4时,就一种情况,单独拎出来处理就行了。
for(int b=a;b*(a-1)<=3*a;b++) {
int btemp=a*b-a-b;
if(btemp<=0) continue;
for(int c=b;c*btemp<=2*a*b;c++) {
int ctemp=a*b*c-a*b-b*c-a*c;
if(ctemp<=0) continue;
for(int d=c;d*ctemp<=a*b*c;d++) {
if(a*b*c*d==b*c*d+a*c*d+a*b*d+a*b*c) {
int[] num=new int[4];
num[0]=a;
num[1]=b;
num[2]=c;
num[3]=d;
list.add(num);
}
}
}
}
}
list.add(new int[] {4,4,4,4});
return list;
}
这样写有点太扭曲了,效率也不高,而且限定了4个参数,不能改变。
面试完后,回家重新改了下代码,改成递归形式。
/**
* 递归-倒数和为1
* @param n 等式左侧有n项
* @return 存储符合等式条件的序列集合
*/
public List<int[]> getPlust(int n) {
int[] m=new int[n];
List<int[]> list=new ArrayList<>();
getPlusOne2(m,0,2,n,1,list);
for(int i=0;i<m.length;i++) { //单独把各元素=n的情况拿出来,最后add,减少循环次数
m[i]=n;
}
list.add(m);
return list;
}
/**
* 递归-倒数和为1
* @param m 存放序列的数组
* @param n 初始为0,用于控制递归深度
* @param min 序列元素的下限,初始为2
* @param temp1 初始值为n,用于控制序列元素上限的条件
* @param temp2 用于存储临时变量-序列元素的连乘 如:a*b*c
* @param list 用于存储不同序列
*/
public void getPlusOne2(int[] m,int n,int min,int temp1,int temp2, List<int[]> list){
if(n==m.length) { //递归出口:n初始为0,记录递归深度,当n==m.length表示序列元素已取完
if(temp2==arrayPoly(m)) { //判断式等价于 1/a+1/b+1/c+1/d=1 类似等式
int[] k=m.clone(); //由于整个函数共用一个int[] m,需要以clone的形式复制后add;
list.add(k); //若直接add(m),list中所有元素都指向m,将导致list中所有元素变为同一值
}
return;
}else if(n==0) {
for(int i=min;i<=temp1-1;i++) { //这里把n==0,递归入口的上下限控制与后面的分开处理
m[n]=i; //这里不需要判断剩余值是否<=0,且temp1初始重赋为a-1
getPlusOne2(m,n+1,i,i-1,i,list);
}
}else {
min=min>n+1?min:n+1; //这里有一个判断:第n个序列元素值必然>=n,如第3个元素c>=3,可以过滤如b=2,c=2的判断
for(int i=min;i*temp1<=(m.length-n)*temp2;i++) { //这里和上面循环的方式一样
m[n]=i;
int t=i*temp1-temp2;
if(n!=m.length-1) { //当n=m.length-1,即n个元素取完时,需要放t==0的序列到上面递归出口判断
if(t<=0) continue;
}else {
if(t<0) continue;
}
getPlusOne2(m,n+1,i,t,temp2*i,list);
}
}
}
辅助函数:
/**
* 计算类似多项式
* m[0]*m[1]*m[2]+m[0]*m[2]*m[3]+m[1]*m[2]*m[3]+m[0]*m[1]*m[3]
* @param m
* @return
*/
public int arrayPoly(int[] m) {
int sum=0;
for(int i=0;i<m.length;i++) {
int product=1;
for(int j=0;j<m.length;j++) {
if(i==j) continue;
product*=m[j];
}
sum+=product;
}
return sum;
}
测试一下运行速度:
public static void main(String[] args) {
PrimeNum primeNum = new PrimeNum();
long t2=System.nanoTime();
List<int[]> list=primeNum.getPlusOne();
for(int[] n1:list) {
System.out.println(Arrays.toString(n1));
}
System.out.println("总共:"+list.size()+"种");
long t3=System.nanoTime();
List<int[]> list2=primeNum.getPlust(4);
for(int[] n1:list2) {
System.out.println(Arrays.toString(n1));
}
System.out.println("总共:"+list2.size()+"种");
long t4=System.nanoTime();
System.out.println("while循环:");
List<int[]> list3=primeNum.whateverTheNameIs(42);
for(int[] n1:list3) {
System.out.println(Arrays.toString(n1));
}
long t5=System.nanoTime();
System.out.println("四层循环-倒数之和:"+(t3-t2)+"纳秒");
System.out.println("递归-倒数之和:"+(t4-t3)+"纳秒");
System.out.println("while循环-倒数之和:"+(t5-t4)+"纳秒");
}
贴一下网上的做法while循环(指定上限,逐个循环)用于对比速度--这个上限事实上无法指定,需要计算出来
public List<int[]> whateverTheNameIs(int max){
// 视edge1为小值域 如果不符合则调换
List<int[]> list = new ArrayList<>();
int min =1;
int a, b, c, d;
a = b = c = d = min;
while (a <= max) {
if (a * b * c * d == a * b * c + a * b * d + a * c * d + b * c * d) {
int[] num=new int[4];
num[0]=a;
num[1]=b;
num[2]=c;
num[3]=d;
list.add(num);
}
d++;
if (d > max) {
c++;
d = c;
}
if (c > max) {
b++;
d = c = b;
}
if (b > max) {
a++;
d = c = b = a;
}
}
return list;
}
运行结果:
[2, 3, 7, 42]
[2, 3, 8, 24]
[2, 3, 9, 18]
[2, 3, 10, 15]
[2, 3, 12, 12]
[2, 4, 5, 20]
[2, 4, 6, 12]
[2, 4, 8, 8]
[2, 5, 5, 10]
[2, 6, 6, 6]
[3, 3, 4, 12]
[3, 3, 6, 6]
[3, 4, 4, 6]
[4, 4, 4, 4]
总共:14种
[2, 3, 7, 42]
[2, 3, 8, 24]
[2, 3, 9, 18]
[2, 3, 10, 15]
[2, 3, 12, 12]
[2, 4, 5, 20]
[2, 4, 6, 12]
[2, 4, 8, 8]
[2, 5, 5, 10]
[2, 6, 6, 6]
[3, 3, 4, 12]
[3, 3, 6, 6]
[3, 4, 4, 6]
[4, 4, 4, 4]
总共:14种
while循环:
[2, 3, 7, 42]
[2, 3, 8, 24]
[2, 3, 9, 18]
[2, 3, 10, 15]
[2, 3, 12, 12]
[2, 4, 5, 20]
[2, 4, 6, 12]
[2, 4, 8, 8]
[2, 5, 5, 10]
[2, 6, 6, 6]
[3, 3, 4, 12]
[3, 3, 6, 6]
[3, 4, 4, 6]
[4, 4, 4, 4]
四层循环-倒数之和:560376纳秒
递归-倒数之和:294460纳秒
while循环-倒数之和:3330401纳秒
四层循环虽然有点繁琐,效率其实也还可以。最快的当然是递归。最慢的是形式看似简洁的逐次while循环,和前两个不是一个等级,而且这样指定上限不能确保获得的答案完整。
再贴个递归计算5项倒数之和的枚举:
[2, 3, 7, 43, 1806]
[2, 3, 7, 44, 924]
[2, 3, 7, 45, 630]
[2, 3, 7, 46, 483]
[2, 3, 7, 48, 336]
[2, 3, 7, 49, 294]
[2, 3, 7, 51, 238]
[2, 3, 7, 54, 189]
[2, 3, 7, 56, 168]
[2, 3, 7, 60, 140]
[2, 3, 7, 63, 126]
[2, 3, 7, 70, 105]
[2, 3, 7, 78, 91]
[2, 3, 7, 84, 84]
[2, 3, 8, 25, 600]
[2, 3, 8, 26, 312]
[2, 3, 8, 27, 216]
[2, 3, 8, 28, 168]
[2, 3, 8, 30, 120]
[2, 3, 8, 32, 96]
[2, 3, 8, 33, 88]
[2, 3, 8, 36, 72]
[2, 3, 8, 40, 60]
[2, 3, 8, 42, 56]
[2, 3, 8, 48, 48]
[2, 3, 9, 19, 342]
[2, 3, 9, 20, 180]
[2, 3, 9, 21, 126]
[2, 3, 9, 22, 99]
[2, 3, 9, 24, 72]
[2, 3, 9, 27, 54]
[2, 3, 9, 30, 45]
[2, 3, 9, 36, 36]
[2, 3, 10, 16, 240]
[2, 3, 10, 18, 90]
[2, 3, 10, 20, 60]
[2, 3, 10, 24, 40]
[2, 3, 10, 30, 30]
[2, 3, 11, 14, 231]
[2, 3, 11, 15, 110]
[2, 3, 11, 22, 33]
[2, 3, 12, 13, 156]
[2, 3, 12, 14, 84]
[2, 3, 12, 15, 60]
[2, 3, 12, 16, 48]
[2, 3, 12, 18, 36]
[2, 3, 12, 20, 30]
[2, 3, 12, 21, 28]
[2, 3, 12, 24, 24]
[2, 3, 13, 13, 78]
[2, 3, 14, 14, 42]
[2, 3, 14, 15, 35]
[2, 3, 14, 21, 21]
[2, 3, 15, 15, 30]
[2, 3, 15, 20, 20]
[2, 3, 16, 16, 24]
[2, 3, 18, 18, 18]
[2, 4, 5, 21, 420]
[2, 4, 5, 22, 220]
[2, 4, 5, 24, 120]
[2, 4, 5, 25, 100]
[2, 4, 5, 28, 70]
[2, 4, 5, 30, 60]
[2, 4, 5, 36, 45]
[2, 4, 5, 40, 40]
[2, 4, 6, 13, 156]
[2, 4, 6, 14, 84]
[2, 4, 6, 15, 60]
[2, 4, 6, 16, 48]
[2, 4, 6, 18, 36]
[2, 4, 6, 20, 30]
[2, 4, 6, 21, 28]
[2, 4, 6, 24, 24]
[2, 4, 7, 10, 140]
[2, 4, 7, 12, 42]
[2, 4, 7, 14, 28]
[2, 4, 8, 9, 72]
[2, 4, 8, 10, 40]
[2, 4, 8, 12, 24]
[2, 4, 8, 16, 16]
[2, 4, 9, 9, 36]
[2, 4, 9, 12, 18]
[2, 4, 10, 10, 20]
[2, 4, 10, 12, 15]
[2, 4, 12, 12, 12]
[2, 5, 5, 11, 110]
[2, 5, 5, 12, 60]
[2, 5, 5, 14, 35]
[2, 5, 5, 15, 30]
[2, 5, 5, 20, 20]
[2, 5, 6, 8, 120]
[2, 5, 6, 9, 45]
[2, 5, 6, 10, 30]
[2, 5, 6, 12, 20]
[2, 5, 6, 15, 15]
[2, 5, 7, 7, 70]
[2, 5, 8, 8, 20]
[2, 5, 10, 10, 10]
[2, 6, 6, 7, 42]
[2, 6, 6, 8, 24]
[2, 6, 6, 9, 18]
[2, 6, 6, 10, 15]
[2, 6, 6, 12, 12]
[2, 6, 7, 7, 21]
[2, 6, 8, 8, 12]
[2, 6, 9, 9, 9]
[2, 7, 7, 7, 14]
[2, 8, 8, 8, 8]
[3, 3, 4, 13, 156]
[3, 3, 4, 14, 84]
[3, 3, 4, 15, 60]
[3, 3, 4, 16, 48]
[3, 3, 4, 18, 36]
[3, 3, 4, 20, 30]
[3, 3, 4, 21, 28]
[3, 3, 4, 24, 24]
[3, 3, 5, 8, 120]
[3, 3, 5, 9, 45]
[3, 3, 5, 10, 30]
[3, 3, 5, 12, 20]
[3, 3, 5, 15, 15]
[3, 3, 6, 7, 42]
[3, 3, 6, 8, 24]
[3, 3, 6, 9, 18]
[3, 3, 6, 10, 15]
[3, 3, 6, 12, 12]
[3, 3, 7, 7, 21]
[3, 3, 8, 8, 12]
[3, 3, 9, 9, 9]
[3, 4, 4, 7, 42]
[3, 4, 4, 8, 24]
[3, 4, 4, 9, 18]
[3, 4, 4, 10, 15]
[3, 4, 4, 12, 12]
[3, 4, 5, 5, 60]
[3, 4, 5, 6, 20]
[3, 4, 6, 6, 12]
[3, 4, 6, 8, 8]
[3, 5, 5, 5, 15]
[3, 5, 5, 6, 10]
[3, 6, 6, 6, 6]
[4, 4, 4, 5, 20]
[4, 4, 4, 6, 12]
[4, 4, 4, 8, 8]
[4, 4, 5, 5, 10]
[4, 4, 6, 6, 6]
[5, 5, 5, 5, 5]
总共:147种
这种完全不能靠指定上限。
递归-倒数之和:3089125纳秒
参考:https://zhidao.baidu.com/question/243707012608764404.html