LeetCode Contains Duplicate: From Brute Force to Peak Ruby
A simple LeetCode problem turned into a surprisingly good lesson in both algorithms and Ruby itself. From brute force nested loops to hash-based Sets to Ruby’s almost suspiciously concise uniq.size solution, this problem highlights how the same challenge can evolve as you learn both performance optimization and the philosophy of the language.
One thing I have been enjoying while working through LeetCode problems in Ruby is seeing how the same solution evolves as you learn both algorithms and the language itself.
Contains Duplicate is a perfect example of that progression.
The problem itself is simple:
Given an integer array
nums, returntrueif any value appears at least twice in the array, and returnfalseif every element is distinct.
At first glance, this feels almost trivial.
Then you realize there are multiple ways to solve it, each teaching something slightly different about performance, readability, and Ruby itself.
Brute Force
The most direct solution is simply comparing every number against every other number.
def contains_duplicate_brute_force(nums)
left = 0
while left < nums.length
right = left + 1
while right < nums.length
return true if nums[left] == nums[right]
right += 1
end
left += 1
end
false
end
Conceptually, this makes perfect sense:
- Pick a number
- Compare it against the rest of the array
- If you ever find a match, return
true
This is a classic nested loop approach.
The problem is performance.
For an array of size n, this solution performs roughly N*N comparisons in the worst case.
That gives us:
- Time Complexity:
O(n²) - Space Complexity:
O(1)
The nice thing about brute force solutions is they are often the easiest to reason about during interviews.
The downside is they scale terribly.
Using a Set
This is the solution that usually represents the "real" algorithmic improvement.
require 'set'
def contains_duplicate(nums)
seen = Set.new
nums.each do |num|
return true if seen.include?(num)
seen.add(num)
end
false
end
This works because Sets are optimized for fast lookups.
Instead of comparing each element against every other element:
- We store numbers we've already seen
- Before adding a new number, we check whether it already exists
That changes the performance dramatically.
- Time Complexity:
O(n) - Space Complexity:
O(n)
This is usually considered the ideal interview solution.
It demonstrates:
- understanding of hash-based lookup structures
- ability to trade memory for speed
- awareness of algorithmic complexity
It also reads cleanly.
One small Ruby detail worth mentioning:
require 'set'
is needed when running this outside platforms like LeetCode, since Set is part of Ruby’s standard library but is not loaded automatically.
The Extremely Ruby Solution
Then Ruby comes along and says:
“What if we just... removed duplicates first?”
def contains_duplicate_with_length(nums)
nums.uniq.length != nums.length
end
def contains_duplicate_with_size(nums)
nums.uniq.size != nums.size
end
This is aggressively Ruby.
The idea is simple:
nums.uniq
returns a new array with duplicates removed.
So:
- if the lengths are different
- duplicates must have existed
Honestly, this is probably what many experienced Ruby developers would write in real production code unless performance became an issue.
It is short.
It is readable.
It communicates intent immediately.
And under the hood, uniq is already using hashing internally anyway.
length vs size
Now for the funny part.
These two methods are effectively identical:
length
size
For Arrays in Ruby:
arr.length == arr.size
Both return the number of elements.
So why do both exist?
Mostly readability and convention.
length
Feels more semantic when talking about sequences or strings.
name.length
reads naturally.
size
Feels more generic and collection-oriented.
users.size
also reads naturally.
Under the hood for Arrays, they are aliases.
There is no meaningful performance difference here.
This means these two solutions are effectively the same:
nums.uniq.length != nums.length
and
nums.uniq.size != nums.size
The only real difference is stylistic preference.
Which Solution Should You Use?
Interview Setting
The Set solution is probably the strongest answer.
It demonstrates actual algorithmic optimization.
seen = Set.new
shows the interviewer you understand data structures and lookup efficiency.
Real Ruby Code
Honestly?
You will probably see this a lot:
nums.uniq.size != nums.size
Ruby developers tend to value expressiveness heavily.
If performance is acceptable, the cleanest code often wins.
The Fun Part About Learning Ruby
One thing I keep noticing while learning Ruby is how often the language encourages you to think at a higher level.
In Java or C++, I would instinctively reach for loops immediately.
Ruby constantly nudges you toward:
- expressing intent
- using collections effectively
- leveraging built-in abstractions
- writing code that reads almost like English
Sometimes that can feel magical.
Sometimes it feels suspiciously magical.
But it has been fun seeing how even a tiny LeetCode problem can evolve from:
compare every number manually
into:
nums.uniq.size != nums.size
with almost the entire implementation detail disappearing behind expressive language features.
Share this post
Spread the word on your favorite platform.