这是剑指offer中数组类知识点中第3道题目,此题目的难度为两颗星,相对比较简单,原题目链接:构建乘积数组。
为方便直接查看,先抄一下题目。
题目描述:
给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*...A[i+1]*...*A[n-1],不能使用除法。(注意,规定B[0]=A[1]*A[2]*...*A[n-1],B[n-1]=A[0]*A[1]*...*A[n-2])
题目分析:
有题目可知数组A与数组B是同维的,且长度也相同。其中数组A已知,求数组B。
数组B中各元素是数组A中所有元素的乘积(数组A中对应位置的元素除外)。
由于题目要求不能使用除法,所以我们不能使用(直接算出数组A中所有元素的乘积,然后再除以该位置的元素;最后得到数组B中的一个元素。)这种方法。
那么就只能逐个求数组B中的元素了。由于B[i]是A中除A[i]之外,其余元素的乘积,那么只要其余元素有0,则B[i]就必然是0。
再进一步,如果数组A中有两个或两个以上0元素,则数组B必须是全0元素。
既然考虑到了0元素,那么当A[i]=0时,数组B是什么情况呢?显然,只有B[i]不为0,其余都为0。
当数组A中没有0元素时,只能逐个按照公式来求B[i]了。
实现代码如下:
1 public int[] multiply(int[] A) { 2 if(A==null || A.length <= 0){ 3 return null; 4 } 5 int[] B = new int[A.length]; 6 int zero_num = 0; //记录数组A中0元素的个数 7 for(int i=0; i<A.length; i++){ 8 if(A[i] == 0){ 9 zero_num++; 10 if(zero_num>1){ //若数组A中0元素个数超过1个,则数组B必全为0元素 11 return B; 12 } 13 B[i]=1; 14 for(int j=0; j<A.length; j++){ 15 if(j!=i){ 16 B[i]*=A[j]; 17 } 18 } 19 return B; //若数组A中只有一个0元素,则数组B仅此位置不为0 20 }else{ //若始终无0元素,则逐个计算B[i] 21 B[i]=1; 22 for(int j=0; j<A.length; j++){ 23 if(j!=i){ 24 B[i]*=A[j]; 25 } 26 } 27 } 28 } 29 return B; 30 }
此代码在牛客网上运行时间为15ms。
进阶方法:
虽然上述方法已经尽量避免了出现0情况下的计算;然而如果数组A中没有出现0元素时,此方法则是对数组B中的每个元素都重新进行连乘求解。此种情况下并没有进行简化。
假设不考虑0元素这种情况,数组B中每个元素都需要遍历一次数组A;那么能否减少遍历次数,从而减少乘法计算次数呢?如果能找到数组B中元素之间的关系,那么就可以减少大量的乘法计算。
那么将数组B各元素的表达式列出来;如下图所示:
其中数组B中各元素等于该行的乘积;通过观察上图可以发现:可以把每一行中1作为分界线,假设1左边的各部分的乘积记为Ci,1右边的各部分的乘积记为Di。
就可以得到Bi=Ci*Di;Bi+1=Ci+1*Di+1;Bi-1=Ci-1*Di-1。图中可以明显发现如下关系:Ci=Ci-1*Ai-1;Di=Di+1*Ai+1。据此关系,便可以减少乘法计算次数了。实现代码如下:
1 public int[] multiply(int[] A) { 2 if(A==null || A.length <= 0){ 3 return null; 4 } 5 int[] B = new int[A.length]; 6 int left = 1; //初始化左半部分Ci,C0为1;每次计算的结果Ci暂时保存在Bi中 7 int right = 1; //初始化右半部分Di,D(n-1)为1。Di与Ci的计算结果为最终的Bi 8 B[0] = left; //C0不用计算,先直接放在B0处 9 for(int i=1; i<A.length; i++){ 10 left = left*A[i-1]; //Ci = C(i-1)*A(i-1) 11 B[i] = left; 12 } 13 //B(n-1)=C(n-1)*D(n-1)=C(n-1);所以直接从B(n-2)开始计算 14 for(int j=A.length-2; j>=0; j--){ 15 right = right * A[j+1]; //Di = D(i+1)*A(i+1) 16 B[j] = B[j] * right; //Bi = Ci * Di 17 } 18 return B; 19 }
此代码在牛客网上的运行时间为18ms。
最终结果:
接下来再结合最开始考虑过的针对0元素的处理方法,将此处的代码替换掉0元素未出现的那部分代码即可,最终代码如下:
1 public int[] multiply(int[] A) { 2 if(A==null || A.length <= 0){ 3 return null; 4 } 5 int[] B = new int[A.length]; 6 7 //考虑数组A中有0元素的情况 8 int zero_num = 0; //记录数组A中0元素的个数 9 for(int i=0; i<A.length; i++){ 10 if(A[i] == 0){ 11 zero_num++; 12 if(zero_num>1){ //若数组A中0元素个数超过1个,则数组B必全为0元素 13 return B; 14 } 15 B[i]=1; 16 for(int j=0; j<A.length; j++){ 17 if(j!=i){ 18 B[i]*=A[j]; 19 } 20 } 21 return B; //若数组A中只有一个0元素,则数组B仅此位置不为0 22 } 23 } 24 25 //若数组A中没有0元素的情况 26 int left = 1; //初始化左半部分Ci,C0为1;每次计算的结果Ci暂时保存在Bi中 27 int right = 1; //初始化右半部分Di,D(n-1)为1。Di与Ci的计算结果为最终的Bi 28 B[0] = left; //C0不用计算,先直接放在B0处 29 for(int i=1; i<A.length; i++){ 30 left = left*A[i-1]; //Ci = C(i-1)*A(i-1) 31 B[i] = left; 32 } 33 //B(n-1)=C(n-1)*D(n-1)=C(n-1);所以直接从B(n-2)开始计算 34 for(int j=A.length-2; j>=0; j--){ 35 right = right * A[j+1]; //Di = D(i+1)*A(i+1) 36 B[j] = B[j] * right; //Bi = Ci * Di 37 } 38 return B; 39 }
此代码在牛客网上的运行时间为14ms。