目录
- 题目
- 解法
- 思路:
- 步骤:
- 代码实现:
- 解释:
- 示例:
- 输出:
- 除nums[i]之外的其他数如何快速找到其索引,不用遍历的方法?
- 前缀积是什么?
- 为什么会想到前缀积和后缀积的方法?
题目
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请 不要使用除法,且在 O(n) 时间复杂度内完成此题。
解法
这个问题要求我们计算一个数组 answer
,其中每个元素 answer[i]
是数组 nums
中除 nums[i]
以外的所有元素的乘积,且不能使用除法,并要求时间复杂度为 O(n)。我们可以使用前缀积和后缀积的技巧来高效地解决问题。
思路:
- 前缀积:对于每个位置
i
,计算它左边所有元素的乘积。 - 后缀积:对于每个位置
i
,计算它右边所有元素的乘积。 - 最终结果:对于每个位置
i
,结果answer[i]
就等于它左边的前缀积乘以右边的后缀积。
通过这种方式,我们可以避免直接使用除法。
步骤:
- 创建一个
answer
数组,首先存储每个位置的前缀积。 - 再遍历数组,从右向左计算后缀积,并同时更新
answer
数组中的结果。
代码实现:
#include <iostream>
#include <vector>std::vector<int> productExceptSelf(const std::vector<int>& nums) {int n = nums.size();std::vector<int> answer(n, 1); // 初始化结果数组,所有元素设为 1// 计算前缀积for (int i = 1; i < n; ++i) {answer[i] = answer[i - 1] * nums[i - 1]; // 当前元素存放左边所有元素的乘积}// 计算后缀积并同时更新结果数组int suffix = 1; // 记录后缀积for (int i = n - 1; i >= 0; --i) {answer[i] = answer[i] * suffix; // 将前缀积乘以后缀积suffix *= nums[i]; // 更新后缀积}return answer;
}int main() {std::vector<int> nums = {1, 2, 3, 4};std::vector<int> result = productExceptSelf(nums);// 输出结果for (int num : result) {std::cout << num << " ";}return 0;
}
解释:
-
前缀积部分:
- 从左到右遍历数组,
answer[i]
存储的是nums
数组中第i
个元素左侧所有元素的乘积。 - 例如对于
nums = {1, 2, 3, 4}
,计算完前缀积后的answer
数组是{1, 1, 2, 6}
。
- 从左到右遍历数组,
-
后缀积部分:
- 从右到左遍历数组,同时维护一个变量
suffix
,它表示当前元素右侧所有元素的乘积。 - 在每一步中,
answer[i]
会乘以suffix
,并更新suffix
为当前元素的乘积。
- 从右到左遍历数组,同时维护一个变量
示例:
假设 nums = {1, 2, 3, 4}
,执行过程如下:
-
前缀积计算后:
answer = {1, 1, 2, 6}
-
后缀积计算后:
- 计算从右往左:
suffix = 1
,初始值,乘以answer[3] = 6
,更新suffix = 4
。suffix = 4
,乘以answer[2] = 2
,更新suffix = 12
。suffix = 12
,乘以answer[1] = 1
,更新suffix = 24
。suffix = 24
,乘以answer[0] = 1
。
- 计算从右往左:
最终得到结果:answer = {24, 12, 8, 6}
。
输出:
24 12 8 6
这个解法的时间复杂度为 O(n),并且不使用除法,符合题目的要求。
除nums[i]之外的其他数如何快速找到其索引,不用遍历的方法?
不遍历索引,保存中间结果
前缀积是什么?
前缀的部分是共通的都需要计算的部分,该处可以优化。
为什么会想到前缀积和后缀积的方法?
直接通过两重循环来计算每个元素的乘积是一个最直观的方法。即,对于每个元素 i,我们循环遍历数组,将 nums[i] 之外的所有元素相乘。这种方法的时间复杂度是 O(n^2),在大数组情况下效率低。
这种思路来源于分治和局部积累的思想:
分治思想:我们知道对于每个位置 i,答案可以看成两部分,左边和右边的元素。通过分别计算这两部分,我们将整体问题分解成局部问题。
局部积累:通过一次遍历计算前缀积,我们就能得到每个位置左边的所有元素的乘积。而从右到左遍历时,用后缀积乘以前缀积,直接得到结果。这种方法有效利用了已经计算的信息,没有多余的计算,保持 O(n) 时间复杂度。
这个前缀积和后缀积有点像累加和这种,过程变量,来进行局部积累。利用已经计算的信息
前缀积和后缀积的技巧是一种常见的前缀和后缀动态编程思想,适用于类似「除去某个位置的计算」问题。通过将问题分为左右两部分,我们能够避免多次遍历,达到线性时间复杂度。