题目:
给你一个整数数组 nums
,返回 数组 answer
,其中 answer[i]
等于 nums
中除 nums[i]
之外其余各元素的乘积 。
题目数据 保证 数组 nums
之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请不要使用除法,且在 O(n)
时间复杂度内完成此题
分析:
- 使用除法(行不通)
如果一个数组为:[a1,a2,a,3,...,an]
; 则answer[i] = a1*..*a(i-1)*a(i+1)*...*an
for(int i =0; i<a.size(); i++){
Sum_number *= a[i];
}
如果使用除法,则当有0元素时,出现异常, 即:answer[i] = a1*..*ai+1*...*an /ai
for(int i =0; i<a.size(); i++){
answer[i] = Sum_number / a[i];
}
且题目仅保证其余元素乘积不溢出, 但有可能总乘积溢出。
该方法 时间复杂度为O(N)
,空间复杂度为O(N)
- 循环嵌套(超时)
针对处元素i
的其他元素累乘,时间复杂度O(N^2)
,与题目要求不符,可能会超时。
for(int i=0; i<a.size(); i++){
answer[i] = 1;
for(int j=0; j<a.size(); j++){
if (i!=j){
answer[i] *=a[j];
}
}
}
- 前后缀(优化)
题目的难度在于如何用乘法(不涉及除法),且不使用循环嵌套,将时间复杂度降低为O(N)
。
由于是前缀乘后缀,在顺序遍历时,其实是有规律可循的。
answer[i] = per[i] * after[i]
answer[i+1] = per[i+1] * after[i+1]
其中前后缀关系
per[i+1] = per[i] * nums[i];
after[i] = after[i+1] * nums[i+1];
则per数组的规律为,索引采用0->(n-1)
:
per[0] = 1;
per[1] = per[0]*a[0];
per[2] = per[1]*a[1] = a[0]*a[1];
per[3] = per[2]*a[2] = a[0]*a[1]*a[2];
...
per[i] = per[i]*a[i-1] = a[0]*a[1]*a[2]*...*a[i-1];
...
per[n-1] = per[n]*a[(n-1)-1] = a[0]*a[1]*a[2]*...*a[(n-1)-1];
-----------------------------------------------------
after[(n-1)-0] = 1;
after[(n-1)-1] = after[n-1]*a[n-1] = a[n-1];
after[(n-1)-2] = after[n-2]*a[n-2] = a[n-1]*a[n-2];
after[(n-1)-3] = after[n-3]*a[n-3] = a[n-1]*a[n-2]*a[n-3];
...
after[(n-1)-i] = after[(n-1)-(i-1)]*a[(n-1)-(i-1)] = a[n-1]*a[n-2]*...*a[(n-1)-(i-1)];
...
after[(n-1)-(n-1)] = after[1]*a[1] = a[n-1]*....*a[1];
如此,通过3个数组,则可以避免循环嵌套, 时间复杂度为O(N)
;
代码:
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> per(n,1);
vector<int> after(n,1);
vector<int> answer(n,1);
for(int i = 1; i < n; i++){
per[i] = per[i-1]*nums[i-1];
after[n-1-i] = after[n-1 -i+1] * nums[n-1-i+1];
}
for(int i=0; i<n ;i++){
answer[i] = per[i]*after[i];
}
return answer;
}
};
总结:
前缀后缀的思想与模型,利用前后缀变化关系与数组的特性,改善时间复杂度。