一、面试问题
给定一个包含 n 个整数的数组 arr[],构造一个相同大小的乘积数组 res[],其中 res[i] 等于数组 arr[] 中除 arr[i] 以外所有元素的乘积。
例如:
输入:arr[] = [10, 3, 5, 6, 2]
输出: [180, 600, 360, 300, 900]
解释:
对于 i=0,res[i] = 3 * 5 * 6 * 2,即 180。
对于 i=1,res[i] = 10 * 5 * 6 * 2,即 600。
对于 i=2,res[i] = 10 * 3 * 6 * 2,即 360。
对于 i=3,res[i] = 10 * 3 * 5 * 2,即 300。
对于 i=4,res[i] = 10 * 3 * 5 * 6,即 900。
输入:arr[] = [12, 0]
输出: [0, 12]
解释:
对于 i=0,res[i] = 0。
对于 i=1,res[i] = 12。
二、【朴素方法】使用嵌套循环 - 时间复杂度为 O(n²),空间复杂度为 O(1)
(一) 算法思路与步骤
思路很简单,对于数组中的每个元素 arr[i],我们计算除它自身以外的所有元素的乘积。为了计算这个乘积,我们使用一个内层循环。
(二) 使用6种语言实现
1. C++
#include <iostream>
#include <vector>
using namespace std;
// 函数:计算每个元素对应的“除自身外所有元素的乘积”
vector<int> productExceptSelf(vector<int>& arr) {
int n = arr.size();
// 初始化结果数组,每个元素为 1
vector<int> res(n, 1);
// 遍历每个元素
for (int i = 0; i < n; i++) {
// 计算除 arr[i] 外所有元素的乘积
for (int j = 0; j < n; j++) {
if (i != j)
res[i] *= arr[j];
}
}
return res;
}
int main() {
// 输入数组
vector<int> arr = {10, 3, 5, 6, 2};
// 调用函数得到结果
vector<int> res = productExceptSelf(arr);
// 打印结果
for (int val : res)
cout << val << " ";
return 0;
}
2. C
#include <stdio.h>
// 函数:计算每个元素对应的“除自身外所有元素的乘积”
void productExceptSelf(int arr[], int n, int res[]) {
// 第一步:初始化结果数组,每个位置初始值为 1
for (int i = 0; i < n; i++) {
res[i] = 1;
}
// 第二步:对每个元素,计算除了它本身以外的所有元素的乘积
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// 如果不是当前元素本身,则参与乘积计算
if (i != j) {
res[i] *= arr[j];
}
}
}
}
int main() {
// 输入数组
int arr[] = {10, 3, 5, 6, 2};
int n = sizeof(arr) / sizeof(arr[0]);
// 用于存储结果的数组
int res[n];
// 调用函数计算结果
productExceptSelf(arr, n, res);
// 输出结果
for (int i = 0; i < n; i++) {
printf("%d ", res[i]);
}
return 0;
}
3. Java
import java.util.Arrays;
class DSA {
// 函数:计算每个元素对应的“除自身外所有元素的乘积”
static int[] productExceptSelf(int[] arr) {
int n = arr.length;
// 初始化结果数组,所有元素初始为 1
int[] res = new int[n];
Arrays.fill(res, 1);
// 外层循环:遍历每一个元素
for (int i = 0; i < n; i++) {
// 内层循环:计算除 arr[i] 外其他所有元素的乘积
for (int j = 0; j < n; j++) {
if (i != j) {
res[i] *= arr[j];
}
}
}
// 返回结果数组
return res;
}
public static void main(String[] args) {
// 输入数组
int[] arr = {10, 3, 5, 6, 2};
// 调用函数,获取结果
int[] res = productExceptSelf(arr);
// 输出结果
for (int val : res) {
System.out.print(val + " ");
}
}
}
4. Python
# 函数:计算每个元素对应的“除自身外所有元素的乘积”
def productExceptSelf(arr):
n = len(arr)
# 初始化结果列表,所有元素初始为 1
res = [1] * n
# 外层循环:遍历每一个元素
for i in range(n):
# 内层循环:计算除 arr[i] 外其他所有元素的乘积
for j in range(n):
if i != j:
res[i] *= arr[j]
# 返回结果列表
return res
if __name__ == "__main__":
arr = [10, 3, 5, 6, 2]
# 调用函数,获取结果
res = productExceptSelf(arr)
# 输出结果,以空格分隔
print(" ".join(map(str, res)))
5. C#
using System;
class DSA {
// 函数:计算每个元素对应的“除自身外所有元素的乘积”
static int[] productExceptSelf(int[] arr) {
int n = arr.Length;
// 初始化结果数组,所有元素初始为 1
int[] res = new int[n];
Array.Fill(res, 1);
// 外层循环:遍历每一个元素
for (int i = 0; i < n; i++) {
// 内层循环:计算除 arr[i] 外其他所有元素的乘积
for (int j = 0; j < n; j++) {
if (i != j) {
res[i] *= arr[j];
}
}
}
// 返回结果数组
return res;
}
static void Main(string[] args) {
int[] arr = {10, 3, 5, 6, 2};
// 调用函数,获取结果
int[] res = productExceptSelf(arr);
// 输出结果,用空格分隔
foreach (int val in res) {
Console.Write(val + " ");
}
}
}
6. JavaScript
// 函数:计算每个元素对应的“除自身外所有元素的乘积”
function productExceptSelf(arr) {
let n = arr.length;
// 初始化结果数组,所有元素初始化为 1
let res = new Array(n).fill(1);
// 双层循环:计算每个元素对应的乘积
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
if (i !== j) {
// 累乘除当前元素外的其他元素
res[i] *= arr[j];
}
}
}
return res;
}
// 测试代码
let arr = [10, 3, 5, 6, 2];
let res = productExceptSelf(arr);
// 以空格分隔打印结果
console.log(res.join(" "));
(三) 代码输出和算法复杂度
输出:
180 600 360 300 900
时间复杂度:O(n²)
空间复杂度:O(1)
三、【更优方法】使用前缀数组和后缀数组 — 时间复杂度 O(n),空间复杂度 O(n)
(一) 算法思路与步骤
此方法可以通过避免重复计算元素乘积来优化。其思路是预先计算前缀积和后缀积,并将它们分别存储在两个数组中。这样,我们就可以在常数时间内,通过这两个预计算数组求出除第 i 个元素外所有元素的乘积。
除第 i 个元素外的乘积 = prefProduct[i] * suffProduct[i]
- prefProduct[i] 存储数组中第 i 个元素之前所有元素的乘积。
- suffProduct[i] 存储数组中第 i 个元素之后所有元素的乘积。
(二) 使用5种语言实现
1. C++
#include <iostream>
#include <vector>
using namespace std;
// 函数:计算数组中除当前元素外所有元素的乘积
vector<int> productExceptSelf(vector<int> &arr) {
int n = arr.size();
vector<int> prefProduct(n), suffProduct(n), res(n);
// 构建前缀乘积数组,prefProduct[i]表示索引i左边所有元素的乘积(不含arr[i])
prefProduct[0] = 1; // 第一个元素左边没有元素,乘积为1
for (int i = 1; i < n; i++)
prefProduct[i] = arr[i - 1] * prefProduct[i - 1];
// 构建后缀乘积数组,suffProduct[i]表示索引i右边所有元素的乘积(不含arr[i])
suffProduct[n - 1] = 1; // 最后一个元素右边没有元素,乘积为1
for (int j = n - 2; j >= 0; j--)
suffProduct[j] = arr[j + 1] * suffProduct[j + 1];
// 利用前缀乘积和后缀乘积构建结果数组
// res[i] = 左边所有元素乘积 * 右边所有元素乘积
for (int i = 0; i < n; i++)
res[i] = prefProduct[i] * suffProduct[i];
return res;
}
int main() {
vector<int> arr = {10, 3, 5, 6, 2};
vector<int> res = productExceptSelf(arr);
// 输出结果数组
for (int val : res)
cout << val << " ";
}
2. Java
import java.util.Arrays;
class DSA {
// Function to calculate the product of all
// elements except the current element
static int[] productExceptSelf(int[] arr) {
int n = arr.length;
int[] prefProduct = new int[n];
int[] suffProduct = new int[n];
int[] res = new int[n];
// Construct the prefProduct array
prefProduct[0] = 1;
for (int i = 1; i < n; i++)
prefProduct[i] = arr[i - 1] * prefProduct[i - 1];
// Construct the suffProduct array
suffProduct[n - 1] = 1;
for (int j = n - 2; j >= 0; j--)
suffProduct[j] = arr[j + 1] * suffProduct[j + 1];
// Construct the result array using
// prefProduct[] and suffProduct[]
for (int i = 0; i < n; i++)
res[i] = prefProduct[i] * suffProduct[i];
return res;
}
public static void main(String[] args) {
int[] arr = {10, 3, 5, 6, 2};
int[] res = productExceptSelf(arr);
System.out.println(Arrays.toString(res));
}
}
3. Python
# 计算数组中除当前元素外所有元素的乘积
def productExceptSelf(arr):
n = len(arr)
prefProduct = [1] * n # 前缀乘积数组,存储当前元素左侧所有元素的乘积
suffProduct = [1] * n # 后缀乘积数组,存储当前元素右侧所有元素的乘积
res = [0] * n # 结果数组
# 构建前缀乘积数组
for i in range(1, n):
prefProduct[i] = arr[i - 1] * prefProduct[i - 1]
# 构建后缀乘积数组
for j in range(n - 2, -1, -1):
suffProduct[j] = arr[j + 1] * suffProduct[j + 1]
# 利用前缀乘积和后缀乘积构建结果数组
for i in range(n):
res[i] = prefProduct[i] * suffProduct[i]
return res
if __name__ == '__main__':
arr = [10, 3, 5, 6, 2]
res = productExceptSelf(arr)
print(res)
4. C#
using System;
class DSA {
// 计算数组中除当前元素外所有元素的乘积
static int[] productExceptSelf(int[] arr) {
int n = arr.Length;
int[] prefProduct = new int[n]; // 前缀乘积数组,存储当前元素左侧所有元素的乘积
int[] suffProduct = new int[n]; // 后缀乘积数组,存储当前元素右侧所有元素的乘积
int[] res = new int[n]; // 结果数组
// 构建前缀乘积数组
prefProduct[0] = 1; // 第一个元素左边无元素,乘积为1
for (int i = 1; i < n; i++)
prefProduct[i] = arr[i - 1] * prefProduct[i - 1];
// 构建后缀乘积数组
suffProduct[n - 1] = 1; // 最后一个元素右边无元素,乘积为1
for (int j = n - 2; j >= 0; j--)
suffProduct[j] = arr[j + 1] * suffProduct[j + 1];
// 利用前缀和后缀乘积数组计算结果数组
for (int i = 0; i < n; i++)
res[i] = prefProduct[i] * suffProduct[i];
return res;
}
static void Main() {
int[] arr = {10, 3, 5, 6, 2};
int[] res = productExceptSelf(arr);
// 输出结果
Console.WriteLine(string.Join(" ", res));
}
}
5. JavaScript
// 计算数组中除当前元素外所有元素的乘积的函数
function productExceptSelf(arr) {
const n = arr.length;
const prefProduct = new Array(n).fill(1); // 前缀乘积数组,初始化为1
const suffProduct = new Array(n).fill(1); // 后缀乘积数组,初始化为1
const res = new Array(n); // 结果数组
// 构建前缀乘积数组
for (let i = 1; i < n; i++) {
prefProduct[i] = arr[i - 1] * prefProduct[i - 1];
}
// 构建后缀乘积数组
for (let j = n - 2; j >= 0; j--) {
suffProduct[j] = arr[j + 1] * suffProduct[j + 1];
}
// 使用前缀乘积和后缀乘积数组计算结果数组
for (let i = 0; i < n; i++) {
res[i] = prefProduct[i] * suffProduct[i];
}
return res;
}
// 测试代码
const arr = [10, 3, 5, 6, 2];
const res = productExceptSelf(arr);
console.log(res.join(" "));
(三) 代码输出和算法复杂度
输出:
180 600 360 300 900
时间复杂度:O(n)
空间复杂度:O(n)
四、【高效方法】使用乘积数组 — 时间复杂度 O(n),空间复杂度 O(1)
(一) 算法思路与步骤
这个思路是针对输入数组的两种特殊情况进行处理:数组中包含零和不包含零。
- 如果数组中没有零,那么任意索引处的结果(除自身外所有元素的乘积)可以通过用所有元素的乘积除以当前位置的元素得到。
- 但除以零是未定义的,因此当数组中存在零时,逻辑会有所不同:
-
- 如果只有一个零,那么该零对应的位置的乘积是除零外所有元素的乘积,其他位置的结果都为零。
- 如果有多个零,那么所有位置的结果都为零,因为乘积中包含零,结果必为零。
(二) 使用5种语言实现
1. C++
#include <iostream>
#include <vector>
using namespace std;
// 计算数组中除当前元素外所有元素的乘积,考虑数组中有零的情况
vector<int> productExceptSelf(vector<int> &arr) {
int zeros = 0, idx = -1; // zeros记录零的个数,idx记录零所在的位置
int prod = 1; // 计算非零元素的乘积
// 遍历数组,统计零的数量并计算非零元素乘积
for (int i = 0; i < arr.size(); ++i) {
if (arr[i] == 0) {
zeros++;
idx = i; // 记录零元素的位置
} else {
prod *= arr[i]; // 累乘非零元素
}
}
vector<int> res(arr.size(), 0); // 结果数组,初始值为0
// 如果没有零元素,结果为总乘积除以当前元素
if (zeros == 0) {
for (int i = 0; i < arr.size(); i++)
res[i] = prod / arr[i];
}
// 如果有且只有一个零元素,结果数组中只有该位置为prod,其他为0
else if (zeros == 1)
res[idx] = prod;
// 如果有多个零元素,结果数组全为0(初始化时已设置)
return res;
}
int main() {
vector<int> arr = {10, 3, 5, 6, 2};
vector<int> res = productExceptSelf(arr);
for (int val : res)
cout << val << " ";
}
2. Java
import java.util.Arrays;
class DSA {
// 计算数组中除当前元素外所有元素的乘积,考虑数组中有零的情况
static int[] productExceptSelf(int[] arr) {
int zeros = 0, idx = -1, prod = 1;
int n = arr.length;
// 统计数组中零的个数,并记录最后一个零的索引
for (int i = 0; i < n; i++) {
if (arr[i] == 0) {
zeros++;
idx = i;
} else {
prod *= arr[i]; // 计算所有非零元素的乘积
}
}
int[] res = new int[n];
Arrays.fill(res, 0); // 初始化结果数组,全部设为0
// 如果没有零元素,结果为总乘积除以当前元素
if (zeros == 0) {
for (int i = 0; i < n; i++)
res[i] = prod / arr[i];
}
// 如果只有一个零元素,则结果数组中只有该零元素位置为乘积,其他为0
else if (zeros == 1)
res[idx] = prod;
// 如果有多个零元素,结果数组保持全0
return res;
}
public static void main(String[] args) {
int[] arr = {10, 3, 5, 6, 2};
int[] res = productExceptSelf(arr);
for (int val : res)
System.out.print(val + " ");
}
}
3. Python
# 计算数组中除当前元素外所有元素的乘积,考虑数组中有零的情况
def productExceptSelf(arr):
zeros = 0 # 统计零的数量
idx = -1 # 记录零的位置索引
prod = 1 # 计算非零元素的乘积
# 遍历数组,统计零的个数并计算非零元素乘积
for i in range(len(arr)):
if arr[i] == 0:
zeros += 1
idx = i
else:
prod *= arr[i]
res = [0] * len(arr) # 结果数组,初始值为0
# 如果没有零元素,结果为总乘积除以当前元素
if zeros == 0:
for i in range(len(arr)):
res[i] = prod // arr[i]
# 如果有且只有一个零元素,结果数组中只有该位置为prod,其他为0
elif zeros == 1:
res[idx] = prod
return res
if __name__ == "__main__":
arr = [10, 3, 5, 6, 2]
res = productExceptSelf(arr)
print(" ".join(map(str, res)))
4. C#
using System;
class DSA {
// 计算数组中除当前元素外所有元素的乘积,考虑数组中有零的情况
static int[] productExceptSelf(int[] arr) {
int zeros = 0, idx = -1, prod = 1;
int n = arr.Length;
// 统计零的数量,并记录最后一个零的索引
for (int i = 0; i < n; i++) {
if (arr[i] == 0) {
zeros++;
idx = i;
} else {
prod *= arr[i]; // 计算非零元素乘积
}
}
int[] res = new int[n];
Array.Fill(res, 0); // 初始化结果数组,全为0
// 如果没有零元素,结果为总乘积除以当前元素
if (zeros == 0) {
for (int i = 0; i < n; i++)
res[i] = prod / arr[i];
}
// 如果只有一个零元素,则结果数组中只有该零元素位置为乘积,其他为0
else if (zeros == 1) {
res[idx] = prod;
}
// 如果有多个零,结果数组保持全零
return res;
}
static void Main(string[] args) {
int[] arr = {10, 3, 5, 6, 2};
int[] res = productExceptSelf(arr);
Console.WriteLine(string.Join(" ", res));
}
}
5. JavaScript
// 计算数组中除当前元素外所有元素的乘积,考虑数组中有零的情况
function productExceptSelf(arr) {
let zeros = 0, idx = -1, prod = 1;
// 统计零的数量,并记录最后一个零的索引
for (let i = 0; i < arr.length; i++) {
if (arr[i] === 0) {
zeros++;
idx = i;
} else {
prod *= arr[i]; // 计算非零元素乘积
}
}
// 初始化结果数组,默认全为0
let res = new Array(arr.length).fill(0);
// 如果没有零元素,结果为总乘积除以当前元素
if (zeros === 0) {
for (let i = 0; i < arr.length; i++) {
res[i] = Math.floor(prod / arr[i]);
}
}
// 如果有且只有一个零元素,结果数组中只有该位置为乘积,其他为0
else if (zeros === 1) {
res[idx] = prod;
}
// 如果有多个零,结果数组保持全为0
return res;
}
// 测试代码
let arr = [10, 3, 5, 6, 2];
let res = productExceptSelf(arr);
console.log(res.join(" "));
(三) 代码输出和算法复杂度
输出:
180 600 360 300 900
时间复杂度:O(n)
空间复杂度:O(1)