🏃‍♂️ Solving LeetCode Problems the TDD Way (Test-First Ruby): Contains Duplicate

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 3: Contains Duplicate

# Given an integer array nums, return true if any value appears # at least twice in the array, and return false if every element # is distinct.

Example 1:
Input: nums = [1,2,3,1]
Output: true

Explanation:
The element 1 occurs at the indices 0 and 3.

Example 2:
Input: nums = [1,2,3,4]
Output: false

Explanation:
All elements are distinct.

Example 3:
Input: nums = [1,1,1,3,3,4,3,2,4,2]
Output: true

Constraints:
1 <= nums.length <= 105
-109 <= nums[i] <= 109

🔧 Setting up the TDD environment

Create files and folder

mkdir array_duplicate
touch array_duplicate.rb
touch test_array_duplicate.rb
# frozen_string_literal: true
 
require 'minitest/autorun'
require_relative 'buy_sell'
#####################
##
#####################
# frozen_string_literal: true

#####################

❌ Red: Writing the failing test

# frozen_string_literal: true

require 'minitest/autorun'
require_relative 'array_duplicate'
######################################
#
#
#
######################################
class TestArrayDuplicate < Minitest::Test
  def setup
    ####
  end

  def test_array_with_length_one
    assert_equal false, Duplicate.new([]).present?
  end
end

✗ ruby array_duplicate/test_array_duplicate.rb
Run options: --seed 27633

# Running:
E
Finished in 0.000491s, 2036.6599 runs/s, 0.0000 assertions/s.
  1) Error:
TestArrayDuplicate#test_array_with_length_one:
NameError: uninitialized constant TestArrayDuplicate::Duplicate
    array_duplicate/test_array_duplicate.rb:16:in 'TestArrayDuplicate#test_array_with_length_one'

1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

✅ Green: Making it pass

# Pass ✅
# frozen_string_literal: true

#############################
#
# Given an integer array nums, return true if any value appears at least twice in the array,
# and return false if every element is distinct.

# Example 1:
# .......
#############################
class Duplicate
  def initialize(nums)
    @numbers = nums
  end

  def present?
    'Provide a non-empty array' if @numbers.empty?
  end
end

…………………………………………………. …………………………………………………………..

Writing the Second Test Case:

# frozen_string_literal: true

require 'minitest/autorun'
require_relative 'array_duplicate'
######################################
# Given an integer array nums, return true if any value appears at least twice in the array,
# and return false if every element is distinct.
#
# Example 1:
# Input: nums = [1,2,3,1]
# Output: true
#
# Example 2:
# Input: nums = [1,2,3,4]
# Output: false
#
######################################
class TestArrayDuplicate < Minitest::Test
  def setup
    ####
  end

  def test_empty_array
    assert_equal 'Provide a non-empty array', Duplicate.new([]).present?
  end

  def test_array_with_length_one
    assert_equal false, Duplicate.new([2]).present?
  end

  def test_array_with_length_two
    assert_equal false, Duplicate.new([1, 2]).present?
    assert_equal true, Duplicate.new([2, 2]).present?
  end
end

# Solution 1 ✅
# frozen_string_literal: true

#############################
#
# Given an integer array nums, return true if any value appears at least twice in the array,
# and return false if every element is distinct.

# Example 1:
# ........
#############################
class Duplicate
  def initialize(nums)
    @numbers = nums
  end

  def present?
    return 'Provide a non-empty array' if @numbers.empty?

    count_hash = {}
    @numbers.each do |number|
      count_hash[number] ? count_hash[number] += 1 : count_hash[number] = 1
    end

    count_hash.values.max > 1
  end
end

⏳ Finding the Time Complexity

Time Complexity: O(n)
  • You iterate through the array once: @numbers.each do |number| → O(n)
  • Hash operations (lookup and assignment) are O(1) on average
  • count_hash.values.max → O(n) to get all values and find max
  • Total: O(n) + O(n) = O(n)
Space Complexity: O(n)
  • In worst case (all elements are unique), you store n key-value pairs in count_hash
  • Total: O(n)

♻️ Refactor: Optimizing the solution

# Solution 2 ✅
# frozen_string_literal: true

#############################
#
# Given an integer array nums, return true if any value appears at least twice in the array,
# and return false if every element is distinct.

# Example 1:
# .....
#############################
class Duplicate
  def initialize(nums)
    @numbers = nums
  end

  def present?
    return 'Provide a non-empty array' if @numbers.empty?

    count_hash = {}
    @numbers.each do |number|
      count_hash[number] ? count_hash[number] += 1 : count_hash[number] = 1

      return true if count_hash[number] > 1
    end

    false
  end
end

♻️ Refactor: Try to refactor the solution again

# Solution 3 ✅
# frozen_string_literal: true

#############################
#
# Given an integer array nums, return true if any value appears at least twice in the array,
# and return false if every element is distinct.

# Example 1:
# Input: nums = [1,2,3,1]
# .......
#############################
class Duplicate
  def initialize(nums)
    @numbers = nums
  end

  def present?
    return 'Provide a non-empty array' if @numbers.empty?

    found = {}
    @numbers.each do |number|
      return true if found[number]

      found[number] = true
    end

    false
  end
end

♻️ Refactor: Use Ruby Set – best approach

# Solution 4 ✅
# frozen_string_literal: true

#############################
#
# Given an integer array nums, return true if any value appears at least twice in the array,
# and return false if every element is distinct.

# Example 1:
# Input: nums = [1,2,3,1]
# ........
#############################
class Duplicate
  def initialize(nums)
    @numbers = nums
  end

  def present?
    return 'Provide a non-empty array' if @numbers.empty?

    found = Set.new
    @numbers.each do |number|
      return true if found.include?(number)

      found.add(number)
    end

    false
  end
end

Set vs Hash for Duplicate Detection

Set Approach:
seen = Set.new
@numbers.each do |number|
  return true if seen.include?(number)
  seen.add(number)
end
Hash Approach:
seen = {}
@numbers.each do |number|
  return true if seen[number]
  seen[number] = true
end

Why Set is Better for This Use Case:

1. Semantic Clarity
  • Set: Designed specifically for storing unique elements
  • Hash: Designed for key-value mappings
  • Since we only care about “have I seen this number?”, Set is semantically correct
2. Memory Efficiency
  • Set: Only stores the key (the number)
  • Hash: Stores both key AND value (number + true/false)
  • Set uses less memory per element
3. Intent is Clearer
# Set - clearly shows we're tracking unique elements
seen.add(number)
seen.include?(number)

# Hash - less clear why we're setting values to true
seen[number] = true
seen[number]  # relies on truthy/falsy behavior
4. Performance

Both have O(1) average lookup time, but:

  • Set operations are optimized for membership testing
  • Hash has slight overhead for value storage

When to Use Each:

Use Set when:
  • You only need to track “presence” or “membership”
  • You want to store unique elements
  • You don’t need associated values
  • This duplicate detection problem ✅
Use Hash when:
  • You need to store key-value pairs
  • You need to count occurrences
  • You need to associate data with keys
  • Example: {number => count} for frequency counting
Alternative Hash Approach (Still Valid):

If you prefer Hash, this is also perfectly fine:

seen = {}
@numbers.each do |number|
  return true if seen.key?(number)  # More explicit than seen[number]
  seen[number] = true
end

Bottom Line:

Both work correctly with the same time/space complexity, but Set is the better choice because:

  1. It’s semantically correct for the problem
  2. Uses less memory
  3. Makes the code’s intent clearer
  4. Is specifically designed for this use case

The Problem: https://leetcode.com/problems/contains-duplicate/description/

The Solution: https://leetcode.com/problems/contains-duplicate/post-solution/?submissionId=1664185727

https://leetcode.com/problems/contains-duplicate/submissions/1664185727/

Happy Algo Coding! 🚀

Unknown's avatar

Author: Abhilash

Hi, I’m Abhilash! A seasoned web developer with 15 years of experience specializing in Ruby and Ruby on Rails. Since 2010, I’ve built scalable, robust web applications and worked with frameworks like Angular, Sinatra, Laravel, Node.js, Vue and React. Passionate about clean, maintainable code and continuous learning, I share insights, tutorials, and experiences here. Let’s explore the ever-evolving world of web development together!

Leave a comment