Just No more free questions left!
Upgrade NowYou have an array of integers, and for each index you want to find the product of every integer except the integer at that index.
Write a function getProductsOfAllIntsExceptAtIndex that takes an array of integers and returns an array of the products.
For example, given:
your function would return:
by calculating:
Do not use division in your solution.
Does your function work if the input array contains zeroes? Remember—no division.
We can do this in time and space!
We only need to allocate one new array of size n.
A brute force approach would use two loops to multiply the integer at every index by the integer at every nestedIndex, unless index === nestedIndex.
This would give us a runtime of . Can we do better?
Well, we’re wasting a lot of time doing the same calculations. As an example, let's take:
We're doing some of the same multiplications two or three times!
Or look at this pattern:
We’re redoing multiplications when instead we could be storing the results! This would be a great time to use a greedy approach. We could store the results of each multiplication highlighted in blue, then just multiply by one new integer each time.
So in the last highlighted multiplication, for example, we wouldn’t have to multiply 1*2*6 again. If we stored that value (12) from the previous multiplication, we could just multiply 12*5.
Can we break our problem down into subproblems so we can use a greedy approach?
Let's look back at the last example:
What do all the highlighted multiplications have in common?
They are all the integers that are before each index in the input array ([1, 2, 6, 5, 9]). For example, the highlighted multiplication at index 3 (1*2*6) is all the integers before index 3 in the input array.
Do all the multiplications that aren't highlighted have anything in common?
Yes, they're all the integers that are after each index in the input array!
Knowing this, can we break down our original problem to use a greedy approach?
The product of all the integers except the integer at each index can be broken down into:
To start, let's just get the product of all the integers before each index.
How can we do this? Let's take another example:
Notice that we're always adding one new integer to our multiplication for each index!
So to get the products of all the integers before each index, we could greedily store each product so far and multiply that by the next integer. Then we can store that new product so far and keep going.
So how can we apply this to our input array?
Let’s make an array productsOfAllIntsBeforeIndex:
So we solved the subproblem of finding the products of all the integers before each index. Now, how can we find the products of all the integers after each index?
It might be tempting to make a new array of all the values in our input array in reverse, and just use the same function we used to find the products before each index.
Is this the best way?
This method will work, but:
Is there a cleaner way to get the products of all the integers after each index?
We can just walk through our array backwards! So instead of reversing the values of the array, we'll just reverse the indices we use to iterate!
Now we've got productsOfAllIntsAfterIndex, but we’re starting to build a lot of new arrays. And we still need our final array of the total products. How can we save space?
Let’s take a step back. Right now we’ll need three arrays:
To get the first one, we keep track of the total product so far going forwards, and to get the second one, we keep track of the total product so far going backwards. How do we get the third one?
Well, we want the product of all the integers before an index and the product of all the integers after an index. We just need to multiply every integer in productsOfAllIntsBeforeIndex with the integer at the same index in productsOfAllIntsAfterIndex!
Let's take an example. Say our input array is [2, 4, 10]:
We'll calculate productsOfAllIntsBeforeIndex as:
And we'll calculate productsOfAllIntsAfterIndex as:
If we take these arrays and multiply the integers at the same indices, we get:
And this gives us what we're looking for—the products of all the integers except the integer at each index.
Knowing this, can we eliminate any of the arrays to reduce the memory we use?
Yes, instead of building the second array productsOfAllIntsAfterIndex, we could take the product we would have stored and just multiply it by the matching integer in productsOfAllIntsBeforeIndex!
So in our example above, when we calculated our first (well, "0th") "product after index" (which is 40), we’d just multiply that by our first "product before index" (1) instead of storing it in a new array.
How many arrays do we need now?
Just one! We create an array, populate it with the products of all the integers before each index, and then multiply those products with the products after each index to get our final result!
productsOfAllIntsBeforeIndex now contains the products of all the integers before and after every index, so we can call it productsOfAllIntsExceptAtIndex!
Almost done! Are there any edge cases we should test?
What if the input array contains zeroes? What if the input array only has one integer?
We'll be fine with zeroes.
But what if the input array has fewer than two integers?
Well, there won't be any products to return because at any index there are no “other” integers. So let's throw an exception.
To find the products of all the integers except the integer at each index, we'll go through our array greedily twice. First we get the products of all the integers before each index, and then we go backwards to get the products of all the integers after each index.
When we multiply all the products before and after each index, we get our answer—the products of all the integers except the integer at each index!
time and space. We make two passes through our input an array, and the array we build always has the same length as the input array.
What if you could use division? Careful—watch out for zeroes!
Another question using a greedy approach. The tricky thing about this one: we couldn't actually solve it in one pass. But we could solve it in two passes!
This approach probably wouldn't have been obvious if we had started off trying to use a greedy approach.
Instead, we started off by coming up with a slow (but correct) brute force solution and trying to improve from there. We looked at what our solution actually calculated, step by step, and found some repeat work. Our final answer came from brainstorming ways to avoid doing that repeat work.
So that's a pattern that can be applied to other problems:
Start with a brute force solution, look for repeat work in that solution, and modify it to only do that work once.