LeetCode 热题 100-----16.除了自身以外数组的乘积

一、题目核心拆解(必看)

先抛开专业术语,用大白话把题目说透,确保你完全理解需求:

给定一个整数数组(比如 [1,2,3,4]),要求返回一个新数组(answer),新数组中每个位置的数(answer[i]),等于原数组中"除了当前位置的数(nums[i])之外,所有其他数的乘积"。

举2个具体例子,一看就懂:

示例1:输入 nums = [1,2,3,4]

  • answer[0] = 2×3×4 = 24(除了nums[0]=1,其他数相乘)

  • answer[1] = 1×3×4 = 12(除了nums[1]=2,其他数相乘)

  • answer[2] = 1×2×4 = 8(除了nums[2]=3,其他数相乘)

  • answer[3] = 1×2×3 = 6(除了nums[3]=4,其他数相乘)

所以输出是 [24,12,8,6]

示例2:输入 nums = [-1,1,0,-3,3]

  • answer[0] = 1×0×(-3)×3 = 0(除了nums[0]=-1,其他数有0,乘积为0)

  • answer[1] = (-1)×0×(-3)×3 = 0(除了nums[1]=1,其他数有0)

  • answer[2] = (-1)×1×(-3)×3 = 9(除了nums[2]=0,其他数相乘)

  • answer[3] = (-1)×1×0×3 = 0(除了nums[3]=-3,其他数有0)

  • answer[4] = (-1)×1×0×(-3) = 0(除了nums[4]=3,其他数有0)

所以输出是 [0,0,9,0,0]

题目强制要求(重点!):

  1. 不能用除法(比如先算所有数的总乘积,再除以每个数------不行!因为数组里可能有0,除以0会报错,而且题目明确禁止);

  2. 时间复杂度必须是 O(n)(简单说:只能遍历数组1~3次,不能嵌套遍历,否则会超时);

  3. 进阶要求(加分项):额外空间复杂度 O(1)(简单说:除了存储最终答案的数组,不能再开和原数组长度一样的额外数组,只能用几个变量)。

二、必备前置知识

在看解法之前,必须先掌握这5个基础知识点,否则看代码会懵------我会用最通俗的语言,配具体例子,确保完全不懂编程的人也能理解。

2.1 什么是数组?

数组就是一个"装数字的容器",里面的每个数字都有一个唯一的"位置编号"(专业叫"索引"),索引从0开始(不是从1!这是最容易踩的坑)。

例子:nums = [1,2,3,4]

  • 索引0 → 数字1(第一个数,索引是0)

  • 索引1 → 数字2(第二个数,索引是1)

  • 索引2 → 数字3(第三个数,索引是2)

  • 索引3 → 数字4(第四个数,索引是3)

我们可以通过"索引"快速找到对应的数字,比如 nums[0] 就是1,nums[2] 就是3。

2.2 数组的遍历(核心操作)

遍历就是"逐个访问数组里的每个数字",常见两种方式,和解法密切相关:

  1. 正序遍历:从索引0开始,依次访问到最后一个索引(n-1,n是数组长度);

例子:nums = [1,2,3,4],正序遍历顺序是:nums[0](1)→ nums[1](2)→ nums[2](3)→ nums[3](4)。

  1. 倒序遍历:从最后一个索引(n-1)开始,依次访问到索引0;

例子:nums = [1,2,3,4],倒序遍历顺序是:nums[3](4)→ nums[2](3)→ nums[1](2)→ nums[0](1)。

2.3 前缀乘积 & 后缀乘积(解法核心技巧)

这两个是本题的"灵魂",一定要吃透,用具体例子讲透:

  1. 前缀乘积(左乘积):对于数组中某个位置i,"前缀乘积"就是"i左边所有数字的乘积";

例子:nums = [1,2,3,4]

  • 索引0:左边没有数字 → 前缀乘积 = 1(任何数乘1都不变,方便后续计算);

  • 索引1:左边只有数字1 → 前缀乘积 = 1;

  • 索引2:左边有数字1、2 → 前缀乘积 = 1×2 = 2;

  • 索引3:左边有数字1、2、3 → 前缀乘积 = 1×2×3 = 6。

所以,前缀乘积数组(left)就是 [1,1,2,6]。

  1. 后缀乘积(右乘积):对于数组中某个位置i,"后缀乘积"就是"i右边所有数字的乘积";

例子:nums = [1,2,3,4]

  • 索引3:右边没有数字 → 后缀乘积 = 1;

  • 索引2:右边只有数字4 → 后缀乘积 = 4;

  • 索引1:右边有数字3、4 → 后缀乘积 = 3×4 = 12;

  • 索引0:右边有数字2、3、4 → 后缀乘积 = 2×3×4 = 24。

所以,后缀乘积数组(right)就是 [24,12,4,1]。

关键结论:answer[i] = 前缀乘积[i] × 后缀乘积[i](比如索引0:1×24=24,索引1:1×12=12,和示例1完全对应)。

2.4 时间复杂度 & 空间复杂度(题目强制要求)

不用记复杂公式,用大白话+例子讲懂:

  1. 时间复杂度:衡量"算法执行快慢",用 O(...) 表示,核心看"遍历次数";
  • O(n):只遍历数组1~3次(n是数组长度),效率极高;比如遍历一次数组,执行n次操作,就是O(n);

  • O(n²):嵌套遍历(比如外层遍历n次,内层也遍历n次),执行n×n次操作,效率极低;本题n最大是100000,O(n²)会执行10000000000次操作,电脑处理不了,会"超时"(无法通过测试)。

  1. 空间复杂度:衡量"算法占用内存多少",用 O(...) 表示,核心看"是否开额外数组";
  • O(1):只用到几个变量(比如int a=1,没有开和原数组长度一样的额外数组);

  • O(n):开了一个或多个"和原数组长度相同"的额外数组(比如前缀乘积数组left,长度和nums一样,就是O(n));

注意:题目说"输出数组不算额外空间",所以存储答案的数组(answer),不算在空间复杂度里。

2.5 为什么不能用除法?(补充说明)

很多会想:"先算所有数的总乘积,再除以每个数,不就得到答案了?"------不行!原因有2个:

  1. 数组中可能有0:除以0是数学错误,程序会报错;

  2. 题目明确禁止用除法(考察的是"前缀/后缀乘积"的思路,不是除法技巧)。

三、解法一:暴力解法

先从最直观的思路入手,虽然会超时,但能帮你理解"为什么需要优化",也能轻松看懂。

3.1 核心思路(大白话)

对数组中每个位置i,都重新遍历一次数组,把"除了nums[i]之外的所有数"乘起来,存到answer[i]里。

简单说:双重循环,外层循环找"当前要计算的位置i",内层循环找"除了i之外的所有数",相乘即可。

3.2 Python代码(逐句注释,能看懂每一步)

python 复制代码
from typing import List  # 导入List类型,告诉电脑:nums是整数列表,返回值也是整数列表(可忽略,按模板写即可)

class Solution:  # 定义一个"解决方案类",算法题的固定写法,用于封装解题函数
    def productExceptSelf(self, nums: List[int]) -> List[int]:  # 定义解题函数,nums是输入数组,返回answer数组
        n = len(nums)  # 计算数组长度n,比如nums=[1,2,3,4],n=4
        answer = [1] * n  # 初始化答案数组,所有元素为1(乘1不影响结果,方便后续相乘)
        
        # 外层循环:遍历每个位置i,计算answer[i](i从0到n-1)
        for i in range(n):
            # 内层循环:遍历每个位置j,找"不是i"的位置,把nums[j]乘到answer[i]里
            for j in range(n):
                if i != j:  # 只要j不是i,就把nums[j]乘进去(排除当前元素)
                    answer[i] *= nums[j]  # 等价于:answer[i] = answer[i] * nums[j]
        
        return answer  # 返回最终的答案数组

# 测试主函数:用于验证代码正确性,可直接运行,包含题目示例+自定义输入
def main():
    # 1. 测试题目示例1
    print("测试题目示例1:")
    nums1 = [1,2,3,4]  # 题目给的输入
    sol = Solution()  # 创建Solution类的对象sol(用于调用解题函数)
    res1 = sol.productExceptSelf(nums1)  # 调用解题函数,传入nums1,得到结果res1
    print(f"输入数组:{nums1}")
    print(f"输出结果:{res1}")  # 预期输出:[24,12,8,6]
    print("-" * 50)  # 分隔线,美化输出
    
    # 2. 测试题目示例2
    print("测试题目示例2:")
    nums2 = [-1,1,0,-3,3]  # 题目给的输入
    res2 = sol.productExceptSelf(nums2)  # 调用解题函数
    print(f"输入数组:{nums2}")
    print(f"输出结果:{res2}")  # 预期输出:[0,0,9,0,0]
    print("-" * 50)
    
    # 3. 自定义输入测试(可自己改输入,验证代码)
    print("自定义输入测试:")
    # 获取用户输入:比如输入"2 3 4 5",转换为整数列表
    input_str = input("请输入数组元素(空格分隔):")
    nums3 = list(map(int, input_str.split()))  # 把输入的字符串转换成整数列表
    res3 = sol.productExceptSelf(nums3)  # 调用解题函数
    print(f"输入数组:{nums3}")
    print(f"输出结果:{res3}")

# 执行测试主函数(固定写法,直接复制即可)
if __name__ == "__main__":
    main()

3.3 C++代码(逐句注释,能看懂每一步)

cpp 复制代码
#include <vector>  // 导入vector(C++的动态数组)头文件,用于存储数组
#include <iostream>  // 导入输入输出头文件,用于实现用户输入和结果输出
using namespace std;  // 使用标准命名空间,避免每次写cout、cin都加std::

// 定义解决方案类,封装解题函数(算法题固定写法)
class Solution {
public:
    // 定义解题函数:参数nums是vector<int>类型(整数数组),返回值也是vector<int>类型(答案数组)
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();  // 计算数组长度n,size()是vector的方法,获取元素个数
        vector<int> answer(n, 1);  // 初始化答案数组,长度为n,所有元素初始化为1
        
        // 外层循环:遍历每个位置i(0到n-1),计算answer[i]
        for (int i = 0; i < n; i++) {
            // 内层循环:遍历每个位置j(0到n-1),排除i,相乘
            for (int j = 0; j < n; j++) {
                if (i != j) {  // 只乘"不是当前位置i"的元素
                    answer[i] *= nums[j];  // 累积相乘,更新answer[i]
                }
            }
        }
        
        return answer;  // 返回答案数组
    }
};

// 测试主函数:验证代码正确性,包含示例+自定义输入,可直接运行
int main() {
    // 1. 测试题目示例1
    cout << "测试题目示例1:" << endl;
    vector<int> nums1 = {1, 2, 3, 4};  // 题目给的输入数组
    Solution sol;  // 创建Solution类的对象sol,用于调用解题函数
    vector<int> res1 = sol.productExceptSelf(nums1);  // 调用解题函数,获取结果
    cout << "输入数组:";
    for (int num : nums1) {  // 遍历输入数组,输出内容(C++11及以上支持的遍历方式)
        cout << num << " ";
    }
    cout << endl << "输出结果:";
    for (int num : res1) {  // 遍历结果数组,输出内容
        cout << num << " ";
    }
    cout << endl << "------------------------" << endl;  // 分隔线
    
    // 2. 测试题目示例2
    cout << "测试题目示例2:" << endl;
    vector<int> nums2 = {-1, 1, 0, -3, 3};  // 题目给的输入
    vector<int> res2 = sol.productExceptSelf(nums2);  // 调用解题函数
    cout << "输入数组:";
    for (int num : nums2) {
        cout << num << " ";
    }
    cout << endl << "输出结果:";
    for (int num : res2) {
        cout << num << " ";
    }
    cout << endl << "------------------------" << endl;
    
    // 3. 自定义输入测试(可自己输入,验证代码)
    cout << "自定义输入测试:" << endl;
    int n;  // 存储用户输入的数组长度
    cout << "请输入数组长度:";
    cin >> n;  // 获取用户输入的数组长度
    vector<int> nums3(n);  // 定义长度为n的数组
    cout << "请输入" << n << "个整数(空格分隔):";
    for (int i = 0; i < n; i++) {  // 遍历数组,获取用户输入的每个元素
        cin >> nums3[i];
    }
    vector<int> res3 = sol.productExceptSelf(nums3);  // 调用解题函数
    cout << "输入数组:";
    for (int num : nums3) {
        cout << num << " ";
    }
    cout << endl << "输出结果:";
    for (int num : res3) {
        cout << num << " ";
    }
    cout << endl;
    
    return 0;  // 主函数正常结束,返回0(固定写法)
}

3.4 代码运行过程(必看,以示例1为例)

输入 nums = [1,2,3,4],n=4,answer初始为 [1,1,1,1]

  1. 当i=0(计算answer[0]):

j=0:i==j,跳过;j=1:i!=j,answer[0] = 1×2=2;j=2:answer[0] = 2×3=6;j=3:answer[0] =6×4=24 → answer变成 [24,1,1,1]

  1. 当i=1(计算answer[1]):

j=0:i!=j,answer[1] =1×1=1;j=1:跳过;j=2:answer[1] =1×3=3;j=3:answer[1] =3×4=12 → answer变成 [24,12,1,1]

  1. 当i=2(计算answer[2]):

j=0:1×1=1;j=1:1×2=2;j=2:跳过;j=3:2×4=8 → answer变成 [24,12,8,1]

  1. 当i=3(计算answer[3]):

j=0:1×1=1;j=1:1×2=2;j=2:2×3=6;j=3:跳过 → answer变成 [24,12,8,6]

最终返回 [24,12,8,6],和示例一致,但效率极低。

3.5 缺点(为什么超时?)

时间复杂度是 O(n²),当n=100000时,要执行100000×100000=10000000000次操作,电脑处理不了,会"超时",无法通过题目测试,所以这个解法只能用来理解思路,不能提交。

四、解法二:左右乘积数组(易理解,O(n)空间)

这是最容易理解的"合格解法",解决了暴力解法的超时问题,思路就是我们前置知识里讲的"前缀乘积+后缀乘积",适合入门。

4.1 核心思路(大白话)

  1. 开一个"前缀乘积数组left",存储每个位置i的"左边所有数的乘积";

  2. 开一个"后缀乘积数组right",存储每个位置i的"右边所有数的乘积";

  3. 答案数组answer[i] = left[i] × right[i];

这样只需要遍历3次数组(left一次、right一次、answer一次),时间复杂度O(n),不会超时。

4.2 Python代码(逐句注释,含测试主函数)

python 复制代码
from typing import List

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)  # 1. 计算数组长度n
        # 2. 初始化前缀乘积数组left、后缀乘积数组right、答案数组answer,均为长度n,初始值1
        left = [1] * n    # left[i]:nums[i]左边所有数的乘积
        right = [1] * n   # right[i]:nums[i]右边所有数的乘积
        answer = [1] * n  # 存储最终答案
        
        # 3. 计算前缀乘积数组left(正序遍历,从索引1开始)
        # 原因:索引0左边没有数,left[0]固定为1,不需要计算
        for i in range(1, n):
            # 关键公式:left[i] = 前一个位置的前缀乘积 × 前一个位置的数字
            # 比如i=1:left[1] = left[0] × nums[0] = 1×1=1(左边只有nums[0])
            # 比如i=2:left[2] = left[1] × nums[1] = 1×2=2(左边有nums[0]、nums[1])
            left[i] = left[i-1] * nums[i-1]
        
        # 4. 计算后缀乘积数组right(倒序遍历,从索引n-2开始)
        # 原因:索引n-1右边没有数,right[n-1]固定为1,不需要计算
        for i in range(n-2, -1, -1):
            # 关键公式:right[i] = 后一个位置的后缀乘积 × 后一个位置的数字
            # 比如n=4,i=2:right[2] = right[3] × nums[3] = 1×4=4(右边只有nums[3])
            # 比如i=1:right[1] = right[2] × nums[2] = 4×3=12(右边有nums[2]、nums[3])
            right[i] = right[i+1] * nums[i+1]
        
        # 5. 计算答案数组:每个位置的前缀乘积 × 后缀乘积
        for i in range(n):
            answer[i] = left[i] * right[i]
        
        return answer  # 返回答案

# 测试主函数(和暴力解法一致,可直接运行,验证正确性)
def main():
    # 1. 测试题目示例1
    print("测试题目示例1:")
    nums1 = [1,2,3,4]
    sol = Solution()
    res1 = sol.productExceptSelf(nums1)
    print(f"输入数组:{nums1}")
    print(f"输出结果:{res1}")  # 预期[24,12,8,6]
    print("-" * 50)
    
    # 2. 测试题目示例2
    print("测试题目示例2:")
    nums2 = [-1,1,0,-3,3]
    res2 = sol.productExceptSelf(nums2)
    print(f"输入数组:{nums2}")
    print(f"输出结果:{res2}")  # 预期[0,0,9,0,0]
    print("-" * 50)
    
    # 3. 自定义输入测试
    print("自定义输入测试:")
    input_str = input("请输入数组元素(空格分隔):")
    nums3 = list(map(int, input_str.split()))
    res3 = sol.productExceptSelf(nums3)
    print(f"输入数组:{nums3}")
    print(f"输出结果:{res3}")

if __name__ == "__main__":
    main()

4.3 C++代码(逐句注释,含测试主函数)

cpp 复制代码
#include <vector>
#include <iostream>
using namespace std;

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();  // 计算数组长度n
        // 初始化前缀、后缀、答案数组,长度n,初始值1
        vector<int> left(n, 1);
        vector<int> right(n, 1);
        vector<int> answer(n);
        
        // 计算前缀乘积left:正序遍历(i从1到n-1)
        for (int i = 1; i < n; i++) {
            // left[i] = 前一个位置的前缀乘积 × 前一个位置的数字
            left[i] = left[i-1] * nums[i-1];
        }
        
        // 计算后缀乘积right:倒序遍历(i从n-2到0)
        for (int i = n-2; i >= 0; i--) {
            // right[i] = 后一个位置的后缀乘积 × 后一个位置的数字
            right[i] = right[i+1] * nums[i+1];
        }
        
        // 计算答案:前缀×后缀
        for (int i = 0; i < n; i++) {
            answer[i] = left[i] * right[i];
        }
        
        return answer;
    }
};

// 测试主函数(可直接运行)
int main() {
    // 测试示例1
    cout << "测试题目示例1:" << endl;
    vector<int> nums1 = {1,2,3,4};
    Solution sol;
    vector<int> res1 = sol.productExceptSelf(nums1);
    cout << "输入数组:";
    for (int num : nums1) cout << num << " ";
    cout << endl << "输出结果:";
    for (int num : res1) cout << num << " ";
    cout << endl << "------------------------" << endl;
    
    // 测试示例2
    cout << "测试题目示例2:" << endl;
    vector<int> nums2 = {-1,1,0,-3,3};
    vector<int> res2 = sol.productExceptSelf(nums2);
    cout << "输入数组:";
    for (int num : nums2) cout << num << " ";
    cout << endl << "输出结果:";
    for (int num : res2) cout << num << " ";
    cout << endl << "------------------------" << endl;
    
    // 自定义输入测试
    cout << "自定义输入测试:" << endl;
    int n;
    cout << "请输入数组长度:";
    cin >> n;
    vector<int> nums3(n);
    cout << "请输入" << n << "个整数(空格分隔):";
    for (int i = 0; i < n; i++) {
        cin >> nums3[i];
    }
    vector<int> res3 = sol.productExceptSelf(nums3);
    cout << "输入数组:";
    for (int num : nums3) cout << num << " ";
    cout << endl << "输出结果:";
    for (int num : res3) cout << num << " ";
    cout << endl;
    
    return 0;
}

4.4 代码运行过程(必看,以示例1为例)

输入 nums = [1,2,3,4],n=4,left、right、answer初始均为 [1,1,1,1]

第一步:计算前缀乘积数组left(正序遍历,i从1到3)

  • i=1:left[1] = left[0] × nums[0] = 1×1 = 1 → left变成 [1,1,1,1]

  • i=2:left[2] = left[1] × nums[1] = 1×2 = 2 → left变成 [1,1,2,1]

  • i=3:left[3] = left[2] × nums[2] = 2×3 = 6 → left最终为 [1,1,2,6]

第二步:计算后缀乘积数组right(倒序遍历,i从2到0)

  • i=2:right[2] = right[3] × nums[3] = 1×4 = 4 → right变成 [1,1,4,1]

  • i=1:right[1] = right[2] × nums[2] = 4×3 = 12 → right变成 [1,12,4,1]

  • i=0:right[0] = right[1] × nums[1] = 12×2 = 24 → right最终为 [24,12,4,1]

第三步:计算答案数组answer(遍历所有i)

  • i=0:answer[0] = left[0] × right[0] = 1×24 = 24

  • i=1:answer[1] = left[1] × right[1] = 1×12 = 12

  • i=2:answer[2] = left[2] × right[2] = 2×4 = 8

  • i=3:answer[3] = left[3] × right[3] = 6×1 = 6

最终answer = [24,12,8,6],和示例1一致,且只遍历3次数组,效率极高。

4.5 复杂度分析(能懂)

  1. 时间复杂度:O(n) → 总共遍历3次数组(left一次、right一次、answer一次),每次遍历n个元素,总操作次数是3n,属于O(n)(常数倍不影响复杂度);

  2. 空间复杂度:O(n) → 额外开了两个长度为n的数组(left和right),符合题目基本要求,但不符合进阶的O(1)空间要求,所以我们需要进一步优化。

五、解法三:原地优化(进阶最优,O(1)额外空间)

这是本题的"最优解",在解法二的基础上优化空间,去掉额外的left和right数组,只用答案数组和一个变量,完美满足题目进阶要求(O(1)额外空间+O(n)时间),也是面试中最常考的写法。

5.1 核心思路(大白话,结合解法二优化)

解法二的缺点是"开了两个额外数组",我们可以利用"答案数组不算额外空间"的规则,做两个优化:

  1. 不用单独开left数组:把"前缀乘积"直接存在answer数组里(answer先当left用);

  2. 不用单独开right数组:用一个变量(suffix)动态计算"后缀乘积",倒序遍历的时候,直接用这个变量和answer里的前缀乘积相乘,得到最终答案。

简单说:answer先存前缀乘积,再用一个变量存动态后缀乘积,一次倒序遍历完成最终计算,省掉两个额外数组。

5.2 Python代码(逐句注释,含测试主函数)

python 复制代码
from typing import List

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)  # 1. 计算数组长度n
        answer = [1] * n  # 2. 初始化答案数组,先用来存前缀乘积(替代left数组)
        
        # 3. 第一步:正序遍历,计算前缀乘积,存入answer(此时answer等价于解法二的left数组)
        for i in range(1, n):
            # 公式和解法二一致:answer[i] = 前一个位置的前缀乘积 × 前一个位置的数字
            # 此时answer[i]就是nums[i]左边所有数的乘积
            answer[i] = answer[i-1] * nums[i-1]
        
        # 4. 第二步:倒序遍历,用变量动态计算后缀乘积,更新answer为最终结果
        suffix = 1  # 定义suffix变量,存储当前位置的后缀乘积(替代right数组),初始为1(最右边元素后缀乘积为1)
        for i in range(n-1, -1, -1):
            # 核心:当前answer[i]是前缀乘积,乘上suffix(后缀乘积),就是最终答案
            answer[i] *= suffix
            # 更新suffix:把当前nums[i]加入后缀乘积,供左边的元素使用
            # 比如i=3(最右边):suffix初始为1,answer[3] = 6×1=6;之后suffix = 1×4=4(供i=2使用)
            suffix *= nums[i]
        
        return answer  # 返回最终答案

# 测试主函数(和前面一致,可直接运行,验证正确性)
def main():
    # 1. 测试题目示例1
    print("测试题目示例1:")
    nums1 = [1,2,3,4]
    sol = Solution()
    res1 = sol.productExceptSelf(nums1)
    print(f"输入数组:{nums1}")
    print(f"输出结果:{res1}")  # 预期[24,12,8,6]
    print("-" * 50)
    
    # 2. 测试题目示例2
    print("测试题目示例2:")
    nums2 = [-1,1,0,-3,3]
    res2 = sol.productExceptSelf(nums2)
    print(f"输入数组:{nums2}")
    print(f"输出结果:{res2}")  # 预期[0,0,9,0,0]
    print("-" * 50)
    
    # 3. 自定义输入测试
    print("自定义输入测试:")
    input_str = input("请输入数组元素(空格分隔):")
    nums3 = list(map(int, input_str.split()))
    res3 = sol.productExceptSelf(nums3)
    print(f"输入数组:{nums3}")
    print(f"输出结果:{res3}")

if __name__ == "__main__":
    main()

5.3 C++代码(逐句注释,含测试主函数)

cpp 复制代码
#include <vector>
#include <iostream>
using namespace std;

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();  // 计算数组长度n
        vector<int> answer(n, 1);  // 初始化答案数组,先存前缀乘积
        
        // 第一步:正序遍历,计算前缀乘积,存入answer(替代left数组)
        for (int i = 1; i < n; i++) {
            answer[i] = answer[i-1] * nums[i-1];
        }
        
        // 第二步:倒序遍历,用suffix变量动态计算后缀乘积,更新答案
        int suffix = 1;  // 动态后缀乘积变量,初始为1
        for (int i = n-1; i >= 0; i--) {
            answer[i] *= suffix;  // 前缀乘积 × 后缀乘积 = 最终答案
            suffix *= nums[i];    // 更新后缀乘积,包含当前元素,供左边元素使用
        }
        
        return answer;
    }
};

// 测试主函数(可直接运行,和前面格式一致)
int main() {
    // 测试示例1
    cout << "测试题目示例1:" << endl;
    vector<int> nums1 = {1,2,3,4};
    Solution sol;
    vector<int> res1 = sol.productExceptSelf(nums1);
    cout << "输入数组:";
    for (int num : nums1) cout << num << " ";
    cout << endl << "输出结果:";
    for (int num : res1) cout << num << " ";
    cout << endl << "------------------------" << endl;
    
    // 测试示例2
    cout << "测试题目示例2:" << endl;
    vector<int> nums2 = {-1,1,0,-3,3};
    vector<int> res2 = sol.productExceptSelf(nums2);
    cout << "输入数组:";
    for (int num : nums2) cout << num << " ";
    cout << endl << "输出结果:";
    for (int num : res2) cout << num << " ";
    cout << endl << "------------------------" << endl;
    
    // 自定义输入测试
    cout << "自定义输入测试:" << endl;
    int n;
    cout << "请输入数组长度:";
    cin >> n;
    vector<int> nums3(n);
    cout << "请输入" << n << "个整数(空格分隔):";
    for (int i = 0; i < n; i++) {
        cin >> nums3[i];
    }
    vector<int> res3 = sol.productExceptSelf(nums3);
    cout << "输入数组:";
    for (int num : nums3) cout << num << " ";
    cout << endl << "输出结果:";
    for (int num : res3) cout << num << " ";
    cout << endl;
    
    return 0;
}

5.4 代码运行过程(必看,以示例1为例)

输入 nums = [1,2,3,4],n=4,answer初始为 [1,1,1,1],suffix初始为1

第一步:正序遍历,计算前缀乘积(answer当left用)

  • i=1:answer[1] = answer[0] × nums[0] = 1×1 = 1 → answer = [1,1,1,1]

  • i=2:answer[2] = answer[1] × nums[1] = 1×2 = 2 → answer = [1,1,2,1]

  • i=3:answer[3] = answer[2] × nums[2] = 2×3 = 6 → answer = [1,1,2,6](此时answer就是解法二的left数组)

第二步:倒序遍历,动态计算后缀乘积(suffix),更新answer

  • i=3(最右边):

answer[3] = answer[3] × suffix = 6×1 = 6(前缀乘积6 × 后缀乘积1 = 最终答案6)

suffix = suffix × nums[3] = 1×4 = 4(更新后缀乘积,供i=2使用)

此时answer = [1,1,2,6],suffix=4

  • i=2:

answer[2] = answer[2] × suffix = 2×4 = 8(前缀乘积2 × 后缀乘积4 = 最终答案8)

suffix = suffix × nums[2] = 4×3 = 12(更新后缀乘积,供i=1使用)

此时answer = [1,1,8,6],suffix=12

  • i=1:

answer[1] = answer[1] × suffix = 1×12 = 12(前缀乘积1 × 后缀乘积12 = 最终答案12)

suffix = suffix × nums[1] = 12×2 = 24(更新后缀乘积,供i=0使用)

此时answer = [1,12,8,6],suffix=24

  • i=0(最左边):

answer[0] = answer[0] × suffix = 1×24 = 24(前缀乘积1 × 后缀乘积24 = 最终答案24)

suffix = suffix × nums[0] = 24×1 = 24(更新后缀乘积,无后续使用)

最终answer = [24,12,8,6],和示例1一致,且只用到1个额外变量,空间最优。

5.5 复杂度分析(最优解核心)

  1. 时间复杂度:O(n) → 只遍历2次数组(正序1次、倒序1次),总操作次数2n,属于O(n),比解法二更高效;

  2. 空间复杂度:O(1) → 只用到1个额外变量(suffix),没有开任何和原数组长度相同的额外数组,完美满足题目进阶要求,是面试首选解法。

六、三种解法对比

为了方便对比选择,整理了三种解法的核心信息,不用记复杂内容,看表格就能懂:

解法 核心思路 时间复杂度 空间复杂度 是否能提交 适用场景
暴力解法 双重循环,逐个相乘排除当前元素 O(n²) O(1) 不能(超时) 理解思路,知道坑点
左右乘积数组 前缀乘积数组+后缀乘积数组,相乘得答案 O(n) O(n) 入门,易理解,笔试保底
原地优化(最优解) 答案数组存前缀乘积,变量存动态后缀乘积 O(n) O(1) 面试首选,空间最优,体现算法能力

七、必看总结

  1. 本题核心技巧:把"除自身外的乘积"拆成"前缀乘积 × 后缀乘积",这是避开除法、实现O(n)时间的关键;

  2. 三个解法的递进关系:暴力解法(直观但超时)→ 左右乘积数组(解决超时,空间一般)→ 原地优化(空间最优,面试重点);

  3. 学习顺序:先看懂暴力解法的坑点,再掌握左右乘积数组(易理解),最后吃透原地优化(核心考点);

  4. 代码注意事项:

  • Python:注意List类型导入,测试主函数的输入处理,循环的范围(正序从1开始,倒序从n-2开始);

  • C++:注意vector的初始化和遍历方式,输入输出的格式,避免遗漏return 0;

  1. 测试技巧:所有代码都带测试主函数,可直接复制运行,先测试题目示例,再自定义输入验证,确保代码正确。

只要掌握"前缀乘积+后缀乘积"的核心思路,无论题目怎么变(比如数组长度变化、有负数/0),都能轻松解决!

相关推荐
falldeep2 小时前
五分钟了解OpenClaw底层架构
人工智能·算法·机器学习·架构
weixin_446260852 小时前
模型能力深度对决:GPT-4o、Claude 3.5和DeepSeek V系列模型的横向评测与未来趋势洞察
人工智能·算法·机器学习
迷途之人不知返2 小时前
优先级队列:priority_queue
数据结构·c++
jieyucx2 小时前
Go 零基础数据结构:顺序表(像「排抽屉」一样学增删改查)
java·数据结构·golang
想唱rap2 小时前
应用层协议与序列化
linux·运维·服务器·网络·数据结构·c++·算法
重生之我是Java开发战士2 小时前
【笔试强训】Week3:重排字符串,分组,DNA序列
算法
We་ct2 小时前
LeetCode 97. 交错字符串:动态规划详解
前端·算法·leetcode·typescript·动态规划
热心网友俣先生2 小时前
2026年第二十三届五一数学建模竞赛B题四问参考答案+多算法对比
算法·数学建模
无敌昊哥战神2 小时前
【LeetCode 37】解数独 (Sudoku Solver) —— 回溯法详解 (Python/C/C++)
c语言·c++·python·算法·leetcode