这道题看完官方的题解后还是很容易理解的:
题解:初始化两个空数组 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:
vector<int> productExceptSelf(vector<int>& nums) {
vector<int> L(nums.size()),R(nums.size()),answer(nums.size());
//初始化为1,方便后续的运算
L[0] = 1,R[nums.size()-1] = 1;
//这里有点动态规划的感觉,后一个状态依赖于前几个状态。
//初始化左数组
for(uint64_t i = 1;i<nums.size();i++){
L[i] = L[i-1] * nums[i-1];
}
//初始化右数组
for(uint64_t j = nums.size() - 2;j >= 0 ; j--){
R[j] = R[j+1] * nums[j+1];
}
//进行计算
for(uint64_t k = 0;k < nums.size();k++){
answer[k] = L[k]*R[k]; //每个元素的前k元素和后k元素相乘
}
return answer;
}
};
时间复杂度为什么是O(n).计算如下:
初始化左右数组各为n,后续进行计算为n,加起来为3n,大O表示法忽略前常数于是得到O(n).
但是,这段代码有问题:
原因在于:
for(uint64_t j = nums.size() - 2;j >= 0 ; j--)
可以看到初始时j的数值是没有问题的,但是当调试到j为0时,之后在进行j–后,问题就出现了:
此时可以发现j变为一个异常大的数,这会导致当前循环会无限进行,因为当j变为0后经过j–又会变成一个超级大的数。
究其原因则是:
一个 64 位无符号数的最小值是 0 ,最大值是 18446744073709551615
当对 0 进行–运算时,它会变为最大值(超出最小值 0 的下一个值)
因此,当一个 64 位无符号整数变为 0 时,对它进行–操作,实际上是一个模拟的“自增”操作。
因为它跳出 0 最小值,变为最大值 18446744073709551615。
这种行为的根本原因是:
64 位无符号数是循环复用整个取值范围的,而有符号整数不具备这种特性
无符号整数会将最高位作为值的一部分,而不是用来表示正负号
当然,这种行为只发生在 0 时。对其他值进行–操作,结果仍然是减 1。
所以在进行无符号的加减法时特别注意为0的情况。
之前在学计组的时候虽然已经知道会有这种情况的发生,但是一直没注意,今天真的是给我上了一课。
修改后的代码:
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
vector<int> L(nums.size()),R(nums.size()),answer(nums.size());
L[0] = 1,R[nums.size()-1] = 1;
for(uint64_t i = 1;i<nums.size();i++){
L[i] = L[i-1] * nums[i-1];
}
for(int j = nums.size() - 2;j >= 0 ; j--){
R[j] = R[j+1] * nums[j+1];
}
for(uint64_t k = 0;k < nums.size();k++){
answer[k] = L[k]*R[k];
}
return answer;
}
};