【前端面试常见算法题系列】238. 除自身以外数组的乘积(中等)

136 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情

一、题目描述

给你一个整数数组 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 开始讲解,输入 nums = [1, 2, 3, 4] ,输出 answer = [24, 12, 8, 6] ,这是因为:

  • 24 = 1 * 2 * 3 * 4
  • 12 = 1 * 2 * 3 * 4
  • 8 = 1 * 2 * 3 * 4
  • 6 = 1 * 2 * 3 * 4

看到这个,我们可以得知,answer[i] 是当前元素之前的所有元素积 * 当前元素之后的所有元素积 ,因此我们只需要得到每个元素的前缀积和后缀积,然后将这两者对应相乘起来即可。

要得到每个元素的前缀积和后缀积,我们可以用两个数组分别存储,即从第二个元素记录前面元素的乘积(第一个元素的前缀积为 1 ,直接存储即可),以及从倒数第二个元素记录后面元素的乘积(最后一个元素的后缀积为 1 ,也是直接存储即可)。
得到前缀积和后缀积两个数组后,每个元素一一对应相乘起来即为答案数组了。

但题意还有进阶要求,除了输出数组不被视为额外空间,不能开辟额外的空间辅助解题,这样一来就需要改进方法了。
我们可以将后缀积数组省去,改为整型变量,标识后缀积,在求出前缀积的前提下,倒序遍历 nums 数组的同时乘上每个元素的后缀积,一样可以达到目的。代码中有加上注释,相信不难理解。

三、AC 代码

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var productExceptSelf = function(nums) {
    // 不能原地修改 nums 数组,因为要分别计算前缀积和后缀积,需要遍历两次 nums
    // 第一次遍历 nums 时先用来存放每个元素的前缀积,等第二次遍历 nums 时乘上后缀积,即为答案数组
    let answer = []; 
    
    // 第一个元素没有前缀
    answer[0] = 1;
    
    // 从第二个元素开始算前缀积
    for (let i = 1; i < nums.length; i++) {
        answer[i] = answer[i - 1] * nums[i - 1];
    }
    
    // 最后一个元素没有后缀,设为 1 
    let suffix = 1; // 后缀积的标识
    
    // 前缀积乘上后缀积即是答案数组 answer 了
    for (let i = nums.length - 1; i >= 0; i--) {
        answer[i] *= suffix;
        suffix *= nums[i]; // 更新后缀积
    }
    return answer;
};