【前缀和】个人练习-Leetcode-1352. Product of the Last K Numbers
题目链接:https://leetcode.cn/problems/product-of-the-last-k-numbers/
题目大意:设计一个类,维护一个数列,可以做到
add()
:将一个数添加到数列末尾getProduct(k)
:返回最后k
个数的乘积
题目保证调用getProduct(k)
时数列中至少有k
个数。
思路:一开始比较暴力,直接维护一个乘积数组,每进来一个新数num
就给所有的乘积乘上`num``,后来果然超时了。这个方法看似利用了之前的乘积,但事实上依然所有的东西都乘了一遍,假设原先数列有3个数,连续塞入3个后,是做了3+4+53+4+53+4+5次乘法的。如果原先数列很长,接下来塞入的数也很多,时间复杂度会接近O(N2)O(N^2)O(N2)。
此时的乘积数列是这样的
位置 0 1 2 3
元素 x0 x1 x2 x3
乘积 x0x1x2x3 x1x2x3 x2x3 x3
因此思考下如何真正利用先前的乘积。首先可以维护一个【从开头乘到i
位置的乘积】的数列。这个数列看似没用(因为题目要求的是从末尾开始的k
个数的乘积),但好处是一次计算即可完成,之后不会再修改。现在的乘积数列是这样
位置 0 1 2 3
元素 x0 x1 x2 x3
乘积 x0 x0x1 x0x1x2 x0x1x2x3
此时,若要求后k
个元素乘积,只需要【乘积数列最后一个元素】/【乘积数列第N-k-1
个元素】即可。当然,为了防止N-k-1
越界,可以除以【乘积数列第N-k
个元素】再乘【数列本身第N-k
个元素】。
然而有特殊情况就是碰到0
,首先如果某个位置的元素是0
,那么在其之前的数乘到末尾就都是0
了,因此需要记录【最后一个零元素的位置】,在这个零元素的右边的那些数字的乘积是非零的。
随后,对于【乘积数列】,在碰到0
时也要重新计算,因为在零元素左边的那些因数是无法传递过来的。如下例子
位置 0 1 2 3
元素 2 0 3 4
乘积 2 0 3 12
如果N-k
在零元素的左边(包括它本身),那么返回0
即可;如果是右边,那么和之前做法相同。注意,在做这个比较时,如果判断条件是if (prd.size() - k <= lastZ)
的话会出错,当这个式子是1 - 1 <= -1
会判断为true
。因为.size()
方法返回的是size_t
,右边的lastZ
会被强制转换成size_t
,此时-1
会被解释为一个非常大的正整数,导致出错。因此维护一个int len
来表示数组当前的长度,避免使用.size()
完整代码
class ProductOfNumbers {
public:vector<int> arr;vector<int> prd;int allp;int lastZ;int len;ProductOfNumbers() {arr.clear();prd.clear();allp = 1;lastZ = -1;len = 0;}void add(int num) {arr.push_back(num);len++;if (num == 0) {allp = 1;lastZ = len-1;prd.push_back(0);}else {allp *= num;int tmp;if (prd.size() == 0 || prd.back() == 0)tmp = 1;elsetmp = prd.back(); prd.push_back(num * tmp);} }int getProduct(int k) {if (len - k <= lastZ)return 0;elsereturn allp / prd[len-k] * arr[len-k];}
};/* Your ProductOfNumbers object will be instantiated and called as such:* ProductOfNumbers* obj = new ProductOfNumbers();* obj->add(num);* int param_2 = obj->getProduct(k);*/