给定长度为 n 的整数数组
nums
,其中 n > 1,返回输出数组output
,其中output[i]
等于nums
中除nums[i]
之外其余各元素的乘积。示例:
输入:[1,2,3,4]
输出:[24,12,8,6]
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)
解题思路:
如果能用除法的话,问题会变得很简单,只需要将所有整数的乘积求出,然后除以当前数,即为除了当前数以外的所有数字的乘积(不考虑有0的情况)。在不能使用除法的情况下,可以通过减法来实现除法,但是当数组中元素个数很大的时候,减法的次数会变得非常之多,时间复杂度也会远远超过O(n)。在这种情况下,考虑用二进制移位和加减的方式来实现除法,时间复杂度为降为O(n)。
另外,还要考虑数组中有0的情况,如果0的个数不止一个的话,则得到的所有输出全为0;
如果0的个数只有一个的话,那么除了0所在位置以外,所有位置的输出都为0,只有0所谓位置的输出为其他所有数字的乘积;
如果0的个数为0,那么就按照正常情况来处理,这样就把情况分为三种,加一个if判断,不增加时间复杂度。
注意:二进制移位加减的时候,需要考虑正负号,最好的做法是先把正负号提出来,单独处理,所有的计算都是基于正数。
public static int[] productExceptSelf(int[] nums) {
int length = nums.length;
int[] result = new int[length];
int chengji = 1;
int temp = 1;//temp用来存放当前数字
int flag=0;//flag用来表示0的个数
int flag1 = 0;//flag用来记录0出现的位置
//需要考虑乘积是否为0,也就是数组中有没有0元素的存在
for (int i=0; i < length; i++) {
if(nums[i]==0){
flag1=i;
flag++;
}
}
//如果0的个数不止一个,也就是flag>1,那么所有数都为0
if(flag>1){
// System.out.println("flag>1");
for (int i=0; i < length; i++) {
result[i]=0;
}
return result;
}else if(flag==1){
// System.out.println("flag=1");
//如果0的个数只有1个,也就是只有1个0,那么只有该0的位置有乘积,其他位置的乘积全部为0
nums[flag1]=1;
// System.out.println("flag1="+flag1);
for (int i=0; i < length; i++) {
System.out.println("nums[i]="+nums[i]);
chengji = chengji * nums[i];
System.out.println("chengji="+chengji);
}
for (int i=0; i < length; i++) {
result[i]=0;
}
result[flag1] = chengji;
return result;
}else if(flag==0){
// System.out.println("flag=0");
//如果乘积不为0,就按照正常步骤处理
// 先把数组的乘积求出来
for (int i = 0; i < length; i++) {
chengji = chengji * nums[i];
}
// String chengji1 = Integer.toBinaryString(chengji);
for (int i = 0; i < length; i++) {
temp = nums[i];// 获取当前的数字
// 二进制相除,chengji/temp
int a = myDiv(chengji, temp);
result[i]=a;
}
}
return result;
}
public static int myDiv(int a, int b) {
boolean flag = (a < 0) ^ (b < 0);//如果一正一负,则返回的时候加-
//把所有操作变成正数相除
if (a < 0)
a = -a;
if (b < 0)
b = -b;
//除数比被除数大,就不用除了(这种情况在正常情况下是不会发生的)
if (a < b)
return 0;
int msb = 0;
//以24(11000)和4(100)为例,b需要向左移动 msb=3 位(100000=32),才能大于等于a
while ((b << msb) < a) {
msb++;
}
int q = 0;
for (int i = msb; i >= 0; i--) {
//b左移i位,如果大于a,则跳出本次循环
if ((b << i) > a)
continue;
/* 还是以24(11000)和4(100)为例,
* b向左移动 i=3 位(32=100000)的时候,大于a,忽视下面的步骤,结束本次循环
* b向左移动 i=2 位(16=10000)的时候,小于等于a,进入下面的步骤,处理后,得到余数 a=24-16=8,q=000|100=100
* b向左移动 i=1位(8=1000)小于等于a=8,进入下面的步骤,处理后,得到余数 a=8-8=0,q=100|10=110
* 结束循环
*/
//q=商(循环相加)
q |= (1 << i);//q=q|(1 << i),例如000和100或的结果是100
//a=余数
a -= (b << i);//用a减去左移后的b,得到差值,24-16=8
}
if (flag)
return -q;
return q;
}
另外一种比较简便的方法:
采取分治法,把整个数组分成两部分,一部分从左到右计算乘积,另一部分从右到左计算乘积。然后用左右的乘积获得当前数字的乘积。
具体做法就是:
利用2个辅助数组,一个保存顺序遍历的乘积,一个保存逆序的乘积(可以一次遍历完成)。
如数组N=[1,2,3,4],我们一次遍历的时候,得到顺序的乘积为ins=[1,2,6,24],逆序的乘积为ret=[24, 24, 12, 4],那么最后对于数组N中的除当前元素之外其余各元素的乘积,我们就可以利用ins和ret这两个数组来计算得到,假设输出数组为output,对于output中的每一个元素,存在output [i] = ins [i-1] * ret [i+1](注意边界条件),最后得到的结果为output = [24, 12, 8, 6]
public static int[] productExceptSelf(int[] nums) {
int length = nums.length;
int[] result = new int[length];
int[] result1 = new int[length];//从左到右的乘积
int[] result2 = new int[length];//从右到左的乘积
int chengji1 =1;
int chengji2 =1;
//从左往右乘
for (int i=0; i