238. 除自身以外数组的乘积
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法, 且在 O(n)
时间复杂度内完成此题。
示例 1:
输入: nums = [1,2,3,4]
输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]
提示:
2 <= nums.length <= 105
-30 <= nums[i] <= 30
- 保证 数组
nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内
进阶: 你可以在 O(1)
的额外空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组 不被视为 额外空间。)
解:
解(1)假如可以使用除法
- 先遍历一遍求出总乘积
- 遍历到某一位时,总乘积除以当前元素.
问题: 如果其中存在0怎么办, 如果等于0的元素大于1 那么所有结果都是0, 如果只有一个元素等于0,那么单独求下这个元素的左右乘积.其余结果都是0.
解(2)使用两个数组分别保存左右乘积结果.
class Solution {
public int[] productExceptSelf(int[] nums) {
//left[i]存放i位置的左乘机
int left[] = new int[nums.length];
//right[i]存放i位置的右乘机
int right[] = new int[nums.length];
//存放结果
int answer[] = new int[nums.length];
//边界初始值都为一
left[0] = 1;
//边界初始值都为一
right[nums.length - 1] = 1;
//从左往右遍历
for (int i = 1; i < nums.length; i++) {
left[i] = left[i - 1] * nums[i - 1];
}
//从右往左遍历
for (int i = nums.length - 2; i >= 0; i--) {
right[i] = right[i + 1] * nums[i + 1];
}
//求结果
for (int i = 0; i < nums.length; i++) {
answer[i] = left[i] * right[i];
}
return answer;
}
}
解(3)题目要求尽量节约额外空间
-
我们可以用最终返回的数组暂时存储左边的乘积
-
用一个变量从右往左的遍历中记录右边的累积乘积
-
从右往左的遍历依次根据左边的乘积相乘记录的右乘积更新结果数组
class Solution {
public int[] productExceptSelf(int[] nums) {int answer[] = new int[nums.length]; answer[0] = 1; for (int i = 1; i < answer.length; i++) { answer[i] = answer[i - 1] * nums[i - 1]; } int right = 1; for (int i = nums.length - 2; i >= 0; i--) { right = right * nums[i + 1]; answer[i] = right * answer[i]; } return answer; }
}
解(4)尽量减少for循环
-
题解3中先从左往右变量、再从右往左遍历.
-
最终结果是左遍历的乘积 乘以 右边遍历的乘积
-
但是左右的乘积没有依赖关系, 也就是说可以同时左右一起计算.
class Solution {
public int[] productExceptSelf(int[] nums) {int answer[] = new int[nums.length]; //头左乘积为1 answer[0] = 1; //尾右乘积为1 answer[nums.length - 1 ] = 1; //头指针 尾指针遍历 for(int i=1, j=nums.length-2, left=1, right=1; i<nums.length; i++,j--){ //计算左累积乘积 left = left * nums[i-1]; //计算右累积乘积 right = right * nums[j+1]; //下边几个判断主要为了判断左右遍历到哪个位置了 //左右还没交叉 各自保存各自的左右乘积 if(i<j){ answer[i] = left; answer[j] = right; } //刚好交叉 左右乘积乘一起 if(i==j){ answer[i] = left*right; } //已经超过了, 左右乘积乘一起 if(i>j){ answer[i] = left * answer[i]; answer[j] = right * answer[j]; } } return answer; }
}
上面if(i==j) if(i<j) if(i>j)主要是因为int数组默认是0.不能直接全部都乘起来. 可以使用Arrays.fill(answer,1); 将数组填充1 就不用这么多判断了。但是需要增加一次遍历.