题目
给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。
示例:
输入: [1,2,3,4]
输出: [24,12,8,6]
提示:题目数据保证数组之中任意元素的全部前缀元素和后缀(甚至是整个数组)的乘积都在 32 位整数范围内。
说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)。
解法
拿到题目首先的想法是:两次 for 循环,一次乘积一次做除法。但是后来发现说明中注明不要使用除法,便只能向其他方法。
既然是算除了自己之外的累乘,便可以以当前所在位置为分割点,分别计算左侧元素乘积 和 右侧元素乘积,之后再进行相乘。
对此由以下解法:
算法一(摘自 LeetCode 官方解法):
初始化两个空数组 L 和 R。对于给定索引 i,L[i] 代表的是 i 左侧所有数字的乘积,R[i] 代表的是 i 右侧所有数字的乘积。
我们需要用两个循环来填充 L 和 R 数组的值。对于数组 L,L[0] 应该是 1,因为第一个元素的左边没有元素。对于其他元素:L[i] = L[i-1] * nums[i-1]。
同理,对于数组 R,R[length-1] 应为 1。length 指的是输入数组的大小。其他元素:R[i] = R[i+1] * nums[i+1]。
当 R 和 L 数组填充完成,我们只需要在输入数组上迭代,且索引 i 处的值为:L[i] * R[i]。
class Solution {
public int[] productExceptSelf(int[] nums) {
int arrLen = nums.length;
int[] leftNums = new int[arrLen];
int[] rightNums = new int[arrLen];
leftNums[0] = 1;rightNums[arrLen-1] = 1;
for(int i = 1; i < arrLen; i++){
leftNums[i] = leftNums[i-1] * nums[i-1];
rightNums[arrLen-i-1] = rightNums[arrLen-i] * nums[arrLen-i];
}
for(int i = 0; i < arrLen; i++){
leftNums[i] *= rightNums[i];
}
return leftNums;
}
}
复杂度分析
时间复杂度:O(N),其中 N 指的是数组 nums 的大小。预处理 L 和 R 数组以及最后的遍历计算都是 O(N) 的时间复杂度。
空间复杂度:O(N),其中 N 指的是数组 nums 的大小。使用了 L 和 R 数组去构造答案,L 和 R 数组的长度为数组 nums 的大小。
算法二:共享数组方式
整体思路和官方解题思路相同:左乘*右乘。
定义返回数组 returnNums 并将其看作共享数组,同时从左右两端填充数据;之后定义 left,right 来存储左右乘积并循环迭代更新。
在两指针交会前,只需对数组进行简单的填充即可;
在两者交互时(仅发生在奇数长度)其填充值为 *leftright**。
两者交汇后,数组的值应填入最终值:因为左侧部分已经存储了左乘积,而即将计算得到右乘积;右侧部分已存储了右乘积,即将获得左乘积。故直接相乘即可。returnNums[i] = left 和 returnNums[j] = right。
class Solution {
public int[] productExceptSelf(int[] nums) {
int arrLen = nums.length;
int[] returnNums = new int[arrLen];
int left = 1, right = 1; // 临时存储
returnNums[0] = 1; returnNums[arrLen-1] = 1;
for(int i = 1, j = arrLen-2; i < arrLen; i++, j--){
left *= nums[i-1];
right *= nums[j+1];
if(i < j){ // 两指针为交会
returnNums[i] = left;
returnNums[j] = right;
}else if(i == j){ // 两指针重合,奇数位情况下发生
returnNums[i] = left*right;
}else{ // 两指针错位
returnNums[i] *= left;
returnNums[j] *= right;
}
}
return returnNums;
}
}
复杂度分析
时间复杂度: O(N),同上一解题方式相同,进行了一次长度为 N 的循环,其时间复杂度为 O(N)。
空间复杂度:O(1),题目中所述,返回数组的空间不算,故所使用的额外存储空间为 left 和 right。故只有常数级别的空间复杂度。