Statement
Solution
Naive approach
A naive approach would be to use nested loops to traverse the array. For each element, we traverse all the other elements and calculate their product, which will be stored in the corresponding index of the output array.
The time complexity of this approach is , since we’re using nested loops to traverse the array. The space complexity of this approach is , since we’re using a constant number of additional variables.
Optimized approach using Two Pointers
The idea is that we can break down the problem into two parts: the product of elements to the left of each index and the product of elements to the right of each index. By maintaining two separate running products as we traverse the array from both ends, we can accumulate the product values to populate the res
array. This approach eliminates the need for repeated multiplications and effectively calculates the product except for the element at the current index. The Two Pointers pattern is employed here to handle both the left and right products in a single traversal.
Here’s how the algorithm works:
-
Initialize the following variables that will assist us in performing the algorithm:
-
res
: This array will be used to store the output. It is initialized to . -
l
,r
: These are the pointers used to traverse the array. They are initialized to the left and right ends of the array, respectively. -
left_product
: This variable stores the product of the elements to the left of thel
pointer. -
right_product
: This variable stores the product of the elements to the right of ther
pointer.
-
-
Traverse the array while the
l
andr
pointers are not out of bounds:-
Calculate the product to the left of
nums[l]
and store it inres[l]
using the following formula:res[l] = res[l] * left_product
-
Calculate the product to the right of the
nums[r]
and store it inres[r]
using the following formula:res[r] = res[r] * right_product
-
Update
left_product
to include the current element,nums[l]
, in the accumulated product for the next iteration. -
Update
right_product
to include the current element,nums[r]
, in the accumulated product for the next iteration. -
Finally, increment the
l
pointer and decrement ther
pointer to evaluate the next elements. -
The steps above are repeated until the
l
andr
pointers go out of bounds.
-
When
l == r
, both pointers point to the middle element of the array. For this element, both the products to its left and right are being computed one after another and stored inres[l]
(orres[r]
sincel == r
in this case). Therefore, for the case of the middle element, the final product of all the elements, excluding it, is computed in one step. -
After the
l
andr
pointers cross each other, the following behavior occurs: -
The
l
pointer computes the product to the left ofnums[l]
as expected, but now the product to the right ofnums[l]
has already been computed in a previous iteration by ther
pointer. Now both the right and left products have been calculated and combined, the resultant product in this entry is the final product of all the elements, excluding the current one. -
The
r
pointer computes the product to the right ofnums[r]
as expected, but now the product to the left ofnums[r]
has already been computed in a previous iteration by thel
pointer. Now both the right and left products have been calculated and combined, the resultant product in this entry is the final product of all the elements, excluding the current one.
-
-
Lastly, the
res
array contains the desired result, so return it.
The slides below illustrate how the algorithm runs: