Bootstrap

LeetCode 238. 除自身以外数组的乘积 Java实现 前缀和+后缀和空间优化


238. 除自身以外数组的乘积

题目来源

238. 除自身以外数组的乘积

题目分析

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

题目难度

  • 难度:中等

题目标签

  • 标签:数组、前缀和

题目限制

  • 2 <= nums.length <= 10^5
  • -30 <= nums[i] <= 30
  • 保证 数组 nums 之中任意元素的全部前缀元素和后缀的乘积都在 32 位整数范围内

解题思路

思路1:前缀和+后缀和

  1. 问题定义
    • 对于每个元素 nums[i],我们需要计算所有不包含 nums[i] 的元素的乘积。
  2. 核心算法
    • 使用两个辅助数组 presuf 分别存储前缀和后缀乘积。
    • pre[i] 表示 nums[0]nums[i-1] 的乘积。
    • suf[i] 表示 nums[i+1]nums[n-1] 的乘积。
    • 最终 answer[i] = pre[i] * suf[i]

思路2:空间优化

  1. 问题定义
    • 优化空间复杂度,不使用额外的数组来存储前缀和后缀乘积。
  2. 核心算法
    • 使用一个数组 ans 来存储结果。
    • 首先从左到右遍历,计算每个位置的前缀乘积。
    • 然后从右到左遍历,计算每个位置的后缀乘积,并更新 ans

核心算法步骤

前缀和+后缀和
  1. 初始化前缀和后缀数组
    • pre[0] = 1suf[n-1] = 1,因为乘积的初始值为 1。
  2. 计算前缀乘积
    • 遍历 nums,计算每个位置的前缀乘积。
  3. 计算后缀乘积
    • 逆向遍历 nums,计算每个位置的后缀乘积。
  4. 计算答案
    • 遍历 nums,计算每个位置的答案。
空间优化
  1. 初始化答案数组
    • 使用一个变量 temp 来存储当前的乘积。
  2. 计算前缀乘积
    • 从左到右遍历 nums,更新 anstemp
  3. 计算后缀乘积
    • 从右到左遍历 nums,更新 anstemp

代码实现

以下是除自身以外数组的乘积的 Java 代码实现:

public int[] productExceptSelf(int[] nums) {
    // 方法1:前缀和+后缀和
    int n = nums.length;
    int[] ans = new int[n];
    int[] pre = new int[n];
    int[] suf = new int[n];
    pre[0] = 1;
    suf[n - 1] = 1;
    for (int i = 1; i < n; i++) {
        pre[i] = pre[i - 1] * nums[i - 1];
    }
    for (int i = n - 2; i >= 0; i--) {
        suf[i] = suf[i + 1] * nums[i + 1];
    }
    for (int i = 0; i < n; i++) {
        ans[i] = pre[i] * suf[i];
    }
    return ans;
}
public int[] productExceptSelf2(int[] nums) {
    // 方法2:空间优化
    int n = nums.length;
    int[] ans = new int[n];
    int temp = 1;
    for (int i = 0; i < n; i++) {
        ans[i] = temp;
        temp *= nums[i];
    }
    temp = 1;
    for (int i = n - 1; i >= 0; i--) {
        ans[i] *= temp;
        temp *= nums[i];
    }
    return ans;
}

代码解读

前缀和+后缀和
  • presuf 数组分别用于存储从数组开始到当前位置和从数组末尾到当前位置的乘积。
  • 第一层循环计算 pre 数组,第二层循环计算 suf 数组。
  • 第三层循环计算最终答案 ans,每个位置的答案是 pre[i]suf[i] 的乘积。
空间优化
  • 使用单个数组 ans 来存储结果,同时使用一个变量 temp 来跟踪当前的乘积。
  • 第一层循环从左到右遍历数组,计算每个位置的前缀乘积,并存储在 ans 中。
  • 第二层循环从右到左遍历数组,计算每个位置的后缀乘积,并更新 ans

性能分析

  • 时间复杂度:两种方法的时间复杂度都是 O(n),因为我们只需要遍历数组常数次数。
  • 空间复杂度
    • 方法1的空间复杂度是 O(n),因为我们使用了额外的数组来存储前缀和后缀乘积。
    • 方法2的空间复杂度是 O(1),除了输入和输出数组外,我们只使用了常数空间。

测试用例

public static void main(String[] args) {
    int[] nums = {1, 2, 3, 4};
    int[] result = productExceptSelf(nums);
    System.out.println(Arrays.toString(result)); // 输出: [24, 12, 8, 6]
    
    int[] result2 = productExceptSelf2(nums);
    System.out.println(Arrays.toString(result2)); // 输出: [24, 12, 8, 6]
}

扩展讨论

有没有其他方法可以解决这个问题?
  • 使用除法:虽然题目要求不使用除法,但理论上可以通过计算所有元素的乘积然后除以每个元素来得到答案。这种方法不适用于包含零的数组。
总结

本题通过前缀和后缀乘积的方法有效地解决了计算除自身以外数组乘积的问题,同时提供了空间优化的解法,使得空间复杂度降低到 O(1)。这两种方法都是高效的,适用于大规模数据。


;