我在一次工作面试中被问到这个问题,我想知道其他人会如何解决这个问题。我对 Java 最满意,但欢迎使用其他语言的解决方案。
给定一个数字数组 nums,返回一个数字数组 products,其中 products[i] 是所有 nums[j] 的乘积,j != i。输入:[1, 2, 3, 4, 5] 输出:[(2*3*4*5), (1*3*4*5), (1*2*4*5), (1*2 *3*5), (1*2*3*4)] = [120, 60, 40, 30, 24] 您必须在 O(N) 中执行此操作而不使用除法。
[interview-questions]
标记寻找它。如果你找到了,有链接吗?
polygenelubricants 方法的解释是:
诀窍是构造数组(在 4 个元素的情况下):
{ 1, a[0], a[0]*a[1], a[0]*a[1]*a[2], }
{ a[1]*a[2]*a[3], a[2]*a[3], a[3], 1, }
两者都可以通过分别从左边缘和右边缘开始在 O(n) 中完成。
然后,将两个数组逐个元素相乘得到所需的结果。
我的代码看起来像这样:
int a[N] // This is the input
int products_below[N];
int p = 1;
for (int i = 0; i < N; ++i) {
products_below[i] = p;
p *= a[i];
}
int products_above[N];
p = 1;
for (int i = N - 1; i >= 0; --i) {
products_above[i] = p;
p *= a[i];
}
int products[N]; // This is the result
for (int i = 0; i < N; ++i) {
products[i] = products_below[i] * products_above[i];
}
如果您还需要空间中的 O(1) 解决方案,您可以这样做(我认为这不太清楚):
int a[N] // This is the input
int products[N];
// Get the products below the current index
int p = 1;
for (int i = 0; i < N; ++i) {
products[i] = p;
p *= a[i];
}
// Get the products above the current index
p = 1;
for (int i = N - 1; i >= 0; --i) {
products[i] *= p;
p *= a[i];
}
这是一个小的递归函数(在 C++ 中),用于就地进行修改。不过,它需要 O(n) 额外空间(在堆栈上)。假设数组在 a
中并且 N
保存数组长度,我们有:
int multiply(int *a, int fwdProduct, int indx) {
int revProduct = 1;
if (indx < N) {
revProduct = multiply(a, fwdProduct*a[indx], indx+1);
int cur = a[indx];
a[indx] = fwdProduct * revProduct;
revProduct *= cur;
}
return revProduct;
}
num[N-1]
的数字产品;然后在返回的路上,它计算乘法的第二部分,然后用于修改数字数组。
这是我尝试用Java解决它。为非标准格式道歉,但代码有很多重复,这是我能做的最好的让它可读的事情。
import java.util.Arrays;
public class Products {
static int[] products(int... nums) {
final int N = nums.length;
int[] prods = new int[N];
Arrays.fill(prods, 1);
for (int
i = 0, pi = 1 , j = N-1, pj = 1 ;
(i < N) && (j >= 0) ;
pi *= nums[i++] , pj *= nums[j--] )
{
prods[i] *= pi ; prods[j] *= pj ;
}
return prods;
}
public static void main(String[] args) {
System.out.println(
Arrays.toString(products(1, 2, 3, 4, 5))
); // prints "[120, 60, 40, 30, 24]"
}
}
循环不变量是 pi = nums[0] * nums[1] *.. nums[i-1]
和 pj = nums[N-1] * nums[N-2] *.. nums[j+1]
。左边的i
部分是“前缀”逻辑,右边的j
部分是“后缀”逻辑。
递归单行
Jasmeet 给出了一个(漂亮的!)递归解决方案;我已经把它变成了这个(可怕的!)Java 单线。它就地修改,堆栈中有 O(N)
个临时空间。
static int multiply(int[] nums, int p, int n) {
return (n == nums.length) ? 1
: nums[n] * (p = multiply(nums, nums[n] * (nums[n] = p), n + 1))
+ 0*(nums[n] *= p);
}
int[] arr = {1,2,3,4,5};
multiply(arr, 1, 0);
System.out.println(Arrays.toString(arr));
// prints "[120, 60, 40, 30, 24]"
the code has a lot of duplication
不。该问题具有显着的对称性,通过您的方法和格式突出显示。
将 Michael Anderson 的解决方案翻译成 Haskell:
otherProducts xs = zipWith (*) below above
where below = scanl (*) 1 $ init xs
above = tail $ scanr (*) 1 xs
偷偷规避“不分割”规则:
sum = 0.0
for i in range(a):
sum += log(a[i])
for i in range(a):
output[i] = exp(sum - log(a[i]))
在这里,简单而干净的解决方案具有 O(N) 复杂度:
int[] a = {1,2,3,4,5};
int[] r = new int[a.length];
int x = 1;
r[0] = 1;
for (int i=1;i<a.length;i++){
r[i]=r[i-1]*a[i-1];
}
for (int i=a.length-1;i>0;i--){
x=x*a[i];
r[i-1]=x*r[i-1];
}
for (int i=0;i<r.length;i++){
System.out.println(r[i]);
}
左转->右转并继续保存产品。称之为过去。 -> O(n) 向右旅行 -> 向左保持产品。称之为未来。 -> O(n) 结果[i] = Past[i-1] * future[i+1] -> O(n) Past[-1] = 1;和未来[n+1]=1;
上)
C++,O(n):
long long prod = accumulate(in.begin(), in.end(), 1LL, multiplies<int>());
transform(in.begin(), in.end(), back_inserter(res),
bind1st(divides<long long>(), prod));
这是我在现代 C++ 中的解决方案。它利用了 std::transform
并且很容易记住。
#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
vector<int>& multiply_up(vector<int>& v){
v.insert(v.begin(),1);
transform(v.begin()+1, v.end()
,v.begin()
,v.begin()+1
,[](auto const& a, auto const& b) { return b*a; }
);
v.pop_back();
return v;
}
int main() {
vector<int> v = {1,2,3,4,5};
auto vr = v;
reverse(vr.begin(),vr.end());
multiply_up(v);
multiply_up(vr);
reverse(vr.begin(),vr.end());
transform(v.begin(),v.end()
,vr.begin()
,v.begin()
,[](auto const& a, auto const& b) { return b*a; }
);
for(auto& i: v) cout << i << " ";
}
预先计算每个元素左侧和右侧数字的乘积。对于每个元素,期望值是其邻居产品的乘积。
#include <stdio.h>
unsigned array[5] = { 1,2,3,4,5};
int main(void)
{
unsigned idx;
unsigned left[5]
, right[5];
left[0] = 1;
right[4] = 1;
/* calculate products of numbers to the left of [idx] */
for (idx=1; idx < 5; idx++) {
left[idx] = left[idx-1] * array[idx-1];
}
/* calculate products of numbers to the right of [idx] */
for (idx=4; idx-- > 0; ) {
right[idx] = right[idx+1] * array[idx+1];
}
for (idx=0; idx <5 ; idx++) {
printf("[%u] Product(%u*%u) = %u\n"
, idx, left[idx] , right[idx] , left[idx] * right[idx] );
}
return 0;
}
结果:
$ ./a.out
[0] Product(1*120) = 120
[1] Product(1*60) = 60
[2] Product(2*20) = 40
[3] Product(6*5) = 30
[4] Product(24*1) = 24
(更新:现在我仔细观察,这使用与上面的迈克尔安德森、丹尼尔 Migowski 和多基因润滑剂相同的方法)
棘手:
使用以下内容:
public int[] calc(int[] params) {
int[] left = new int[n-1]
in[] right = new int[n-1]
int fac1 = 1;
int fac2 = 1;
for( int i=0; i<n; i++ ) {
fac1 = fac1 * params[i];
fac2 = fac2 * params[n-i];
left[i] = fac1;
right[i] = fac2;
}
fac = 1;
int[] results = new int[n];
for( int i=0; i<n; i++ ) {
results[i] = left[i] * right[i];
}
是的,我确定我错过了一些 i-1 而不是 i,但这就是解决它的方法。
这是 O(n^2) 但 f# 非常漂亮:
List.fold (fun seed i -> List.mapi (fun j x -> if i=j+1 then x else x*i) seed)
[1;1;1;1;1]
[1..5]
还有一个 O(N^(3/2)) 非最优解。不过,这很有趣。
首先预处理每个大小为 N^0.5 的部分乘法(这是在 O(N) 时间复杂度中完成的)。然后,计算每个数字的其他值的倍数可以在 2*O(N^0.5) 时间内完成(为什么?因为您只需将其他 ((N^0.5) - 1) 个数字的最后一个元素相乘,并将结果乘以 ((N^0.5) - 1) 属于当前数字组的数字)。对每个数字执行此操作,可以获得 O(N^(3/2)) 时间。
例子:
4 6 7 2 3 1 9 5 8
部分结果:4*6*7 = 168 2*3*1 = 6 9*5*8 = 360
要计算 3 的值,需要将其他组的值乘以 168*360,然后乘以 2*1。
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
int[] result = { 1, 1, 1, 1, 1 };
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < i; j++) {
result[i] *= arr[j];
}
for (int k = arr.length - 1; k > i; k--) {
result[i] *= arr[k];
}
}
for (int i : result) {
System.out.println(i);
}
}
我想出了这个解决方案,我发现它很清楚你的想法!?
基于 Billz 的回答——对不起,我不能发表评论,但这是一个正确处理列表中重复项目的 scala 版本,可能是 O(n):
val list1 = List(1, 7, 3, 3, 4, 4)
val view = list1.view.zipWithIndex map { x => list1.view.patch(x._2, Nil, 1).reduceLeft(_*_)}
view.force
返回:
List(1008, 144, 336, 336, 252, 252)
在这里添加我的 javascript 解决方案,因为我没有找到任何人建议这样做。什么是除法,除了计算你可以从另一个数字中提取一个数字的次数?我计算了整个数组的乘积,然后遍历每个元素,并减去当前元素直到零:
//No division operation allowed
// keep substracting divisor from dividend, until dividend is zero or less than divisor
function calculateProducsExceptCurrent_NoDivision(input){
var res = [];
var totalProduct = 1;
//calculate the total product
for(var i = 0; i < input.length; i++){
totalProduct = totalProduct * input[i];
}
//populate the result array by "dividing" each value
for(var i = 0; i < input.length; i++){
var timesSubstracted = 0;
var divisor = input[i];
var dividend = totalProduct;
while(divisor <= dividend){
dividend = dividend - divisor;
timesSubstracted++;
}
res.push(timesSubstracted);
}
return res;
}
def productify(arr, prod, i):
if i < len(arr):
prod.append(arr[i - 1] * prod[i - 1]) if i > 0 else prod.append(1)
retval = productify(arr, prod, i + 1)
prod[i] *= retval
return retval * arr[i]
return 1
if __name__ == "__main__":
arr = [1, 2, 3, 4, 5]
prod = []
productify(arr, prod, 0)
print(prod)
好吧,这个解决方案可以被认为是 C/C++ 的解决方案。假设我们有一个包含 n 个元素的数组“a”,例如 a[n],那么伪代码如下所示。
for(j=0;j<n;j++)
{
prod[j]=1;
for (i=0;i<n;i++)
{
if(i==j)
continue;
else
prod[j]=prod[j]*a[i];
}
另一种解决方案,使用除法。两次遍历。将所有元素相乘,然后开始除以每个元素。
{- Recursive solution using sqrt(n) subsets. Runs in O(n). Recursively computes the solution on sqrt(n) subsets of size sqrt(n). Then recurses on the product sum of each subset. Then for each element in each subset, it computes the product with the product sum of all other products. Then flattens all subsets. Recurrence on the run time is T(n) = sqrt(n)*T(sqrt(n)) + T(sqrt(n)) + n Suppose that T(n) ≤ cn in O(n). T(n) = sqrt(n)*T(sqrt(n)) + T(sqrt(n)) + n ≤ sqrt(n)*c*sqrt(n) + c*sqrt(n) + n ≤ c*n + c*sqrt(n) + n ≤ (2c+1)*n ∈ O(n) Note that ceiling(sqrt(n)) can be computed using a binary search and O(logn) iterations, if the sqrt instruction is not permitted. -} otherProducts [] = [] otherProducts [x] = [1] otherProducts [x,y] = [y,x] otherProducts a = foldl' (++) [] $ zipWith (\s p -> map (*p) s) solvedSubsets subsetOtherProducts where n = length a -- Subset size. Require that 1 < s < n. s = ceiling $ sqrt $ fromIntegral n solvedSubsets = map otherProducts subsets subsetOtherProducts = otherProducts $ map product subsets subsets = reverse $ loop a [] where loop [] acc = acc loop a acc = loop (drop s a) ((take s a):acc)
这是我的代码:
int multiply(int a[],int n,int nextproduct,int i)
{
int prevproduct=1;
if(i>=n)
return prevproduct;
prevproduct=multiply(a,n,nextproduct*a[i],i+1);
printf(" i=%d > %d\n",i,prevproduct*nextproduct);
return prevproduct*a[i];
}
int main()
{
int a[]={2,4,1,3,5};
multiply(a,5,1,0);
return 0;
}
这是一个使用 C# 的稍微实用的示例:
Func<long>[] backwards = new Func<long>[input.Length];
Func<long>[] forwards = new Func<long>[input.Length];
for (int i = 0; i < input.Length; ++i)
{
var localIndex = i;
backwards[i] = () => (localIndex > 0 ? backwards[localIndex - 1]() : 1) * input[localIndex];
forwards[i] = () => (localIndex < input.Length - 1 ? forwards[localIndex + 1]() : 1) * input[localIndex];
}
var output = new long[input.Length];
for (int i = 0; i < input.Length; ++i)
{
if (0 == i)
{
output[i] = forwards[i + 1]();
}
else if (input.Length - 1 == i)
{
output[i] = backwards[i - 1]();
}
else
{
output[i] = forwards[i + 1]() * backwards[i - 1]();
}
}
由于创建的 Funcs 的半递归,我不完全确定这是 O(n),但我的测试似乎表明它及时为 O(n)。
在这里要完整的是 Scala 中的代码:
val list1 = List(1, 2, 3, 4, 5)
for (elem <- list1) println(list1.filter(_ != elem) reduceLeft(_*_))
这将打印出以下内容:
120
60
40
30
24
程序会过滤掉当前的 elem (_ != elem);并将新列表与 reduceLeft 方法相乘。如果您使用 scala 视图或 Iterator 进行惰性评估,我认为这将是 O(n)。
// 这是 Java 中的递归解决方案 // 从 main product(a,1,0); 调用如下
public static double product(double[] a, double fwdprod, int index){
double revprod = 1;
if (index < a.length){
revprod = product2(a, fwdprod*a[index], index+1);
double cur = a[index];
a[index] = fwdprod * revprod;
revprod *= cur;
}
return revprod;
}
O(n) 运行时的简洁解决方案:
对于每个元素,计算之前出现的所有元素的乘积,并将其存储在数组“pre”中。对于每个元素,计算该元素之后出现的所有元素的乘积并将其存储在数组“post”中 创建最终数组“result”,对于元素 i,result[i] = pre[i-1]*post [我+1];
这是解决 O(N)
中问题的另一个简单概念。
int[] arr = new int[] {1, 2, 3, 4, 5};
int[] outArray = new int[arr.length];
for(int i=0;i<arr.length;i++){
int res=Arrays.stream(arr).reduce(1, (a, b) -> a * b);
outArray[i] = res/arr[i];
}
System.out.println(Arrays.toString(outArray));
这是ptyhon版本
# This solution use O(n) time and O(n) space
def productExceptSelf(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
N = len(nums)
if N == 0: return
# Initialzie list of 1, size N
l_prods, r_prods = [1]*N, [1]*N
for i in range(1, N):
l_prods[i] = l_prods[i-1] * nums[i-1]
for i in reversed(range(N-1)):
r_prods[i] = r_prods[i+1] * nums[i+1]
result = [x*y for x,y in zip(l_prods,r_prods)]
return result
# This solution use O(n) time and O(1) space
def productExceptSelfSpaceOptimized(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
N = len(nums)
if N == 0: return
# Initialzie list of 1, size N
result = [1]*N
for i in range(1, N):
result[i] = result[i-1] * nums[i-1]
r_prod = 1
for i in reversed(range(N)):
result[i] *= r_prod
r_prod *= nums[i]
return result
我习惯于 C#:
public int[] ProductExceptSelf(int[] nums)
{
int[] returnArray = new int[nums.Length];
List<int> auxList = new List<int>();
int multTotal = 0;
// If no zeros are contained in the array you only have to calculate it once
if(!nums.Contains(0))
{
multTotal = nums.ToList().Aggregate((a, b) => a * b);
for (int i = 0; i < nums.Length; i++)
{
returnArray[i] = multTotal / nums[i];
}
}
else
{
for (int i = 0; i < nums.Length; i++)
{
auxList = nums.ToList();
auxList.RemoveAt(i);
if (!auxList.Contains(0))
{
returnArray[i] = auxList.Aggregate((a, b) => a * b);
}
else
{
returnArray[i] = 0;
}
}
}
return returnArray;
}
这是线性 O(n) 时间的简单 Scala 版本:
def getProductEff(in:Seq[Int]):Seq[Int] = { //create a list which has product of every element to the left of this element val fromLeft = in.foldLeft((1, Seq.empty[Int]))((ac, i) => (i * ac._1, ac._2 :+ ac._1))._2 //create a list which has product of every element to the right of this element, which is the same as the previous step but in reverse val fromRight = in.reverse.foldLeft((1,Seq.empty[Int]))((ac,i) => (i * ac._1,ac._2 :+ ac._1))._2.reverse //merge the two list by product at index in.indices.map(i => fromLeft(i) * fromRight(i)) }
这是有效的,因为基本上答案是一个数组,它具有左右所有元素的乘积。
import java.util.Arrays;
public class Pratik
{
public static void main(String[] args)
{
int[] array = {2, 3, 4, 5, 6}; // OUTPUT: 360 240 180 144 120
int[] products = new int[array.length];
arrayProduct(array, products);
System.out.println(Arrays.toString(products));
}
public static void arrayProduct(int array[], int products[])
{
double sum = 0, EPSILON = 1e-9;
for(int i = 0; i < array.length; i++)
sum += Math.log(array[i]);
for(int i = 0; i < array.length; i++)
products[i] = (int) (EPSILON + Math.exp(sum - Math.log(array[i])));
}
}
输出:
[360, 240, 180, 144, 120]
时间复杂度:O(n) 空间复杂度:O(1)
v_i=0
,则结果中唯一的非零条目是第 i 个元素。但是我怀疑添加一个通道来检测和计算零元素会降低解决方案的清晰度,并且在大多数情况下可能不会带来任何真正的性能提升。