Welcome to my new series where I combine the power of Ruby with the discipline of Test-Driven Development (TDD) to tackle popular algorithm problems from LeetCode! 🧑💻💎 Whether you’re a Ruby enthusiast looking to sharpen your problem-solving skills, or a developer curious about how TDD can transform the way you approach coding challenges, you’re in the right place.
🎲 Episode 4: Product of Array Except Self
################
# Product of Array Except Self
#
# Given an integer array nums, return an array answer such that answer[i] is equal to
# the product of all the elements of nums except nums[i].
# The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.
# You must write an algorithm that runs in O(n) time and without using the division operation.
# Example 1:
# Input: nums = [1,2,3,4]
# Output: [24,12,8,6]
# Example 2:
# Input: nums = [-1,1,0,-3,3]
# Output: [0,0,9,0,0]
# Constraints:
# 2 <= nums.length <= 105
# -30 <= nums[i] <= 30
# The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer.
# Follow up: Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.)
#
# Ex: Numbers.new([2,3,4]).product_except_self
################
🔧 Setting up the TDD environment
mkdir product_except_self
touch product_except_self.rb
touch test_product_except_self.rb
❌ Red: Writing the failing test
Test File:
# ❌ Fail
# frozen_string_literal: true
require 'minitest/autorun'
require_relative 'product_except_self'
################
# Product of Array Except Self
#
# Given an integer array nums, return an array answer such that answer[i] is equal to
# the product of all the elements of nums except nums[i].
# The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.
# You must write an algorithm that runs in O(n) time and without using the division operation.
################
class TestProductExceptSelf < Minitest::Test
def set_up
###
end
def test_empty_array
assert_equal 'Provide an aaray of length atleast two', ProductNumbers.new([]).except_self
end
end
Source Code:
# frozen_string_literal: true
################
# Product of Array Except Self
#
# Given an integer array nums, return an array answer such that answer[i] is equal to
# the product of all the elements of nums except nums[i].
# The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.
# You must write an algorithm that runs in O(n) time and without using the division operation.
################
class ProductNumbers
def initialize(nums)
@numbers = nums
end
def except_self; end
end
✗ ruby product_except_self/test_product_except_self.rb
Run options: --seed 12605
# Running:
F
Finished in 0.009644s, 103.6914 runs/s, 103.6914 assertions/s.
1) Failure:
TestProductExceptSelf#test_empty_array [product_except_self/test_product_except_self.rb:19]:
--- expected
+++ actual
@@ -1 +1 @@
-"Provide an aaray of length atleast two"
+nil
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
➜ leetcode git:(main) ✗
✅ Green: Making it pass
# Pass ✅
# frozen_string_literal: true
################
# Product of Array Except Self
#
# Given an integer array nums, return an array answer such that answer[i] is equal to
# the product of all the elements of nums except nums[i].
# The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.
# You must write an algorithm that runs in O(n) time and without using the division operation.
# Example 1:
# ......
#
# Ex: Numbers.new([2,3,4]).product_except_self
################
class Numbers
def initialize(nums)
@numbers = nums
end
def product_except_self
'Provide an array of length atleast two' if @numbers.length < 2
end
end
………………………………………………….⤵ …………………………………………………………..
# Solution 1 ✅
# frozen_string_literal: true
################
# Product of Array Except Self
#
# Given an integer array nums, return an array answer such that answer[i] is equal to
# the product of all the elements of nums except nums[i].
# The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.
# You must write an algorithm that runs in O(n) time and without using the division operation.
# Example 1:
# Input: nums = [1,2,3,4]
# Output: [24,12,8,6]
# Example 2:
# Input: nums = [-1,1,0,-3,3]
# Output: [0,0,9,0,0]
# Constraints:
# 2 <= nums.length <= 105
# -30 <= nums[i] <= 30
# The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer.
# Follow up: Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.)
#
# Ex: Numbers.new([2,3,4]).product_except_self
################
class Numbers
def initialize(nums)
@numbers = nums
end
def product_except_self
return 'Provide an array of length atleast two' if @numbers.length < 2
answer = []
@numbers.each_with_index do |_number, index|
answer << @numbers.reject.with_index { |_num, i| index == i }.inject(:*)
end
answer
end
end
⏳ Finding the Time Complexity
Let’s analyse time and space complexity of the very first solution found to the current problem.
Time Complexity: O(n²)
Let’s break down the operations:
@numbers.each_with_index do |_number, index| # O(n) - outer loop
answer << @numbers.reject.with_index { |_num, i| index == i }.inject(:*)
# ↑ reject: O(n) ↑ inject: O(n-1) ≈ O(n)
end
- Outer loop: Runs
ntimes (wherenis array length) - For each iteration:
reject.with_index: O(n) – goes through all elements to create new arrayinject(:*): O(n) – multiplies all elements in the rejected array
Total: O(n) × O(n) = O(n²)
Space Complexity: O(n) (excluding output array)
reject.with_indexcreates a new temporary array of sizen-1in each iteration- This temporary array uses O(n) extra space
- Although it’s created and discarded in each iteration, we still need O(n) space at any given moment
Performance Impact
Our current solution doesn’t meet the problem’s requirement of O(n) time complexity. For an array of 10,000 elements, our solution would perform about 100 million operations instead of the optimal 10,000.
♻️ Refactor: Optimizing the solution
# Final - Solution 2 ✅
# Optimized O(n) time, O(1) space solution
# frozen_string_literal: true
################
# Product of Array Except Self
#
# Given an integer array nums, return an array answer such that answer[i] is equal to
# the product of all the elements of nums except nums[i].
# The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.
# You must write an algorithm that runs in O(n) time and without using the division operation.
# Example 1:
# Input: nums = [1,2,3,4]
# Output: [24,12,8,6]
# Example 2:
# Input: nums = [-1,1,0,-3,3]
# Output: [0,0,9,0,0]
# Constraints:
# 2 <= nums.length <= 105
# -30 <= nums[i] <= 30
# The input is generated such that answer[i] is guaranteed to fit in a 32-bit integer.
# Follow up: Can you solve the problem in O(1) extra space complexity? (The output array does not count as extra space for space complexity analysis.)
#
# Ex: Numbers.new([2,3,4]).product_except_self
################
class Numbers
def initialize(nums)
@numbers = nums
@answer = []
end
# Original O(n²) time, O(n) space solution
def product_except_self
return 'Provide an array of length atleast two' if @numbers.length < 2
answer = []
@numbers.each_with_index do |_number, index|
answer << @numbers.reject.with_index { |_num, i| index == i }.inject(:*)
end
answer
end
# Optimized O(n) time, O(1) space solution
def product_except_self_optimized
return 'Provide an array of length atleast two' if @numbers.length < 2
calculate_left_products
multiply_right_products
@answer
end
private
# STEP 1: Fill @answer[i] with product of all numbers TO THE LEFT of i
def calculate_left_products
left_product = 1
0.upto(@numbers.length - 1) do |i|
@answer[i] = left_product
left_product *= @numbers[i] # Update for next iteration
end
end
# STEP 2: Multiply @answer[i] with product of all numbers TO THE RIGHT of i
def multiply_right_products
right_product = 1
(@numbers.length - 1).downto(0) do |i|
@answer[i] *= right_product
right_product *= @numbers[i] # Update for next iteration
end
end
end
Test Case for Above Optimized Solution:
# frozen_string_literal: true
require 'minitest/autorun'
require_relative 'product_except_self'
################
# Product of Array Except Self
#
# Given an integer array nums, return an array answer such that answer[i] is equal to
# the product of all the elements of nums except nums[i].
# The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer.
# You must write an algorithm that runs in O(n) time and without using the division operation.
################
class TestProductExceptSelf < Minitest::Test
def set_up
###
end
def test_empty_array
assert_equal 'Provide an array of length atleast two', Numbers.new([]).product_except_self
assert_equal 'Provide an array of length atleast two', Numbers.new([]).product_except_self_optimized
end
def test_array_of_length_one
assert_equal 'Provide an array of length atleast two', Numbers.new([4]).product_except_self
assert_equal 'Provide an array of length atleast two', Numbers.new([4]).product_except_self_optimized
end
def test_array_of_length_two
assert_equal [3, 4], Numbers.new([4, 3]).product_except_self
assert_equal [6, 5], Numbers.new([5, 6]).product_except_self
# Test optimized version
assert_equal [3, 4], Numbers.new([4, 3]).product_except_self_optimized
assert_equal [6, 5], Numbers.new([5, 6]).product_except_self_optimized
end
def test_array_of_length_three
assert_equal [6, 3, 2], Numbers.new([1, 2, 3]).product_except_self
assert_equal [15, 20, 12], Numbers.new([4, 3, 5]).product_except_self
# Test optimized version
assert_equal [6, 3, 2], Numbers.new([1, 2, 3]).product_except_self_optimized
assert_equal [15, 20, 12], Numbers.new([4, 3, 5]).product_except_self_optimized
end
def test_array_of_length_four
assert_equal [70, 140, 56, 40], Numbers.new([4, 2, 5, 7]).product_except_self
assert_equal [216, 54, 36, 24], Numbers.new([1, 4, 6, 9]).product_except_self
# Test optimized version
assert_equal [70, 140, 56, 40], Numbers.new([4, 2, 5, 7]).product_except_self_optimized
assert_equal [216, 54, 36, 24], Numbers.new([1, 4, 6, 9]).product_except_self_optimized
end
def test_leetcode_examples
# Example 1: [1,2,3,4] -> [24,12,8,6]
assert_equal [24, 12, 8, 6], Numbers.new([1, 2, 3, 4]).product_except_self_optimized
# Example 2: [-1,1,0,-3,3] -> [0,0,9,0,0]
assert_equal [0, 0, 9, 0, 0], Numbers.new([-1, 1, 0, -3, 3]).product_except_self_optimized
end
def test_both_methods_give_same_results
test_cases = [
[4, 3],
[1, 2, 3],
[4, 2, 5, 7],
[1, 4, 6, 9],
[-1, 1, 0, -3, 3],
[2, 3, 4, 5]
]
test_cases.each do |nums|
original_result = Numbers.new(nums).product_except_self
optimized_result = Numbers.new(nums).product_except_self_optimized
assert_equal original_result, optimized_result, "Results don't match for #{nums}"
end
end
end
LeetCode Submission:
# @param {Integer[]} nums
# @return {Integer[]}
def product_except_self(nums)
return 'Provide an array of length atleast two' if nums.length < 2
answer = []
answer = left_product_of_numbers(nums, answer)
answer = right_product_of_numbers(nums, answer)
answer
end
# scan right and find left side product of numbers
def left_product_of_numbers(nums, answer)
left_product = 1 # a place holder for multiplication
0.upto(nums.length - 1) do |i|
answer[i] = left_product
left_product = nums[i] * left_product
end
answer
end
# scan left and find right side product of numbers
def right_product_of_numbers(nums, answer)
right_product = 1 # a place holder for multiplication
(nums.length - 1).downto(0) do |i|
answer[i] = answer[i] * right_product
right_product = nums[i] * right_product
end
answer
end


The Problem: https://leetcode.com/problems/product-of-array-except-self/description/
The Solution: https://leetcode.com/problems/product-of-array-except-self/description/?submissionId=xxxxxx
https://leetcode.com/problems/product-of-array-except-self/submissions/xxxxxxxx/
Happy Algo Coding! 🚀