备注:网易游戏-软件测试岗-2015年校园招聘
一、问题描述:
n个农田线性排列,位置分别为1, 2, ..., n,位置相邻的农田间的距离为1,位置为i的农田每天需要nNeed[i]单位的肥料,现在需要建存储肥料的仓库,把肥料从仓库送到位置为i的农田的代价为nNeed[i]*距离。如果仓库建在位置i,则仓库到位置为i的农田的距离为0,到位置为i-1,i+1的农田的距离为1,以此类推。求建仓库的最佳位置i,使得每天运送肥料到各个农田的总代价最少。
二、问题求解:
1.暴力求解:从1到n依次遍历这n的位置,求仓库建在各个位置所需代价,然后取最少代价,则相应位置即为最优建仓库位置。代码如下:
int pay_i(int n,int *nNeed,int i){
int pay=0;
for(int j=1;j<=n;++j){
pay+=nNeed[j]*(j<i?i-j:j-i);
}
return pay;
}
int leastPay(int n,int *nNeed){
if(n<1||nNeed==NULL){return -1;}
if(n==1){return 1;}
int pos=1;
int min=pay_i(n,nNeed,pos);
for(int sum,i=2;i<=n;++i){
sum=pay_i(n,nNeed,i);
if(sum<min){
min=sum;
pos=i;
}
}
return pos;
}
此方法容易想到,空间复杂度为O(1),但时间复杂度为O(n^2)。
2.设计技巧1:上述求解中,pay_i()计算中有很多求和过程重复计算是不必要的可以,可以改进为线性复杂度算法。设两个辅助数组a1[],a2[],存储部分和的信息:
a1[1]=0; a1[i]=nNeed[1]+nNeed[2]+...+nNeed[i-1], 2<=i<=n; => a[1]=0; a[i]=a[i-1]+nNeed[i-1], 2<=i<=n;
a2[n]=0; a2[i]=nNeed[i+1]+nNeed[i+2]+...+nNeed[n], 1<=i<n; => a2[n]=0; a2[i]=a2[i+1]+nNeed[i+1], 1<=i<n;
则仓库建在位置i时,送肥料到位置从1到i-1的农田的代价sum1=a1[1]+a1[2]+...+a1[i],送肥料到位置从1+1到n的农田的代价sum2=a2[i]+a2[i+1]+...+a2[n],总代价为sum=sum1+sum2。代码实现如下:
int leastPay_optimize2(int n,int *nNeed){
if(n<1||nNeed==NULL){return -1;}
if(n==1){return 1;}
int *a1=new int[n+1];
int *a2=new int[n+1];
a1[1]=0;
for(int i=2;i<=n;++i){
a1[i]=a1[i-1]+nNeed[i-1];
}
a2[n]=0;
for(int i=n-1;i>=1;--i){
a2[i]=a2[i+1]+nNeed[i+1];
}
int sum,sum1=0,sum2=0;
for(int i=n;i>=1;--i){
sum2+=a2[i];
}
int min=sum2,pos=1;
for(int i=2;i<=n;++i){
sum1+=a1[i];
sum2-=a2[i-1];
sum=sum1+sum2;
if(sum<min){
min=sum;
pos=i;
}
}
delete[] a1;
delete[] a2;
return pos;
}
此方法是设计技巧上的改进,容易想到,空间复杂度为O(n),但时间复杂度为O(n)。
3.设计技巧2:方法2已经能够达到很好的时间效率,但空间复杂度仍然较高,可以继续改进。在上述实现中数组a1[],a2[]是为了容易理解而设定的,其实是可以省略掉的。因为在主循环(最后一个循环)中,a1[i],a2[i]只用到一次,并且由a1[i]和a2[i]的定义式可知,它们可以很容易的由a1[i-1]和a2[i+1]递推得出。代码实现如下:
int leastPay_optimize2(int n,int *nNeed){
if(n<1||nNeed==NULL){return -1;}
if(n==1){return 1;}
int sum,sum1=0,sum2=0,sum_1=0,sum_2=0;
for(int i=n-1;i>=1;--i){
//calculate the payment when pos is 1
sum_2+=nNeed[i+1];
sum2+=sum_2;
}
int min=sum2,pos=1;
for(int i=2;i<=n;++i){
sum_1+=nNeed[i-1];
sum1+=sum_1;
sum2-=sum_2;
sum_2-=nNeed[i];
sum=sum1+sum2;
if(sum<min){
min=sum;
pos=i;
}
}
return pos;
}
此方法也是设计技巧上的改进,比较容易理解,空间复杂度为O(1),但时间复杂度为O(n)。
三、问题总结:
总体来说难度一般,方法1很容易想到,方法2也比较容易想出,方法3也比较容易理解。但在时间相对有限的情况下,脑袋容易抽筋。笔试的时候只是给出方法2的解法,并且也不确定一些边界问题是否顾及到。方法3也是在更别人讨论的时候想出的。
纸上手写代码,基本功在平时,多多练习,多多思考,多多讨论,必有收获。