A clear explanation of counting how many stones are jewels using a hash set for fast membership checks.
Problem Restatement
We are given two strings:
jewels
stonesEach character in jewels represents a type of stone that is a jewel.
Each character in stones represents one stone we have.
We need to count how many stones are also jewels.
Letters are case-sensitive, so "a" and "A" are different stone types. The official statement also says all characters in jewels are unique.
Input and Output
| Item | Meaning |
|---|---|
| Input | jewels, the jewel types |
| Input | stones, the stones we have |
| Output | Number of stones whose type appears in jewels |
| Case sensitivity | "a" and "A" are different |
Example function shape:
def numJewelsInStones(jewels: str, stones: str) -> int:
...Examples
Example 1:
jewels = "aA"
stones = "aAAbbbb"Output:
3The jewel types are:
a
AThe stones are:
a A A b b b bThere are three stones that are jewels:
a, A, ASo the answer is 3.
Example 2:
jewels = "z"
stones = "ZZ"Output:
0The jewel type is lowercase "z".
The stones are uppercase "Z".
Since the comparison is case-sensitive, none of the stones are jewels.
First Thought: Check Each Stone Against jewels
A direct solution is to scan every stone and check whether it appears in jewels.
answer = 0
for stone in stones:
if stone in jewels:
answer += 1This works.
But membership checking in a string may scan through the string.
So if jewels is longer, we repeat the same search many times.
Key Insight
We only need to know whether a stone type is a jewel.
That is a membership question.
A set is the right data structure:
jewel_set = set(jewels)Now checking:
stone in jewel_setis expected O(1) time.
Then we scan stones once and count matches.
Algorithm
- Convert
jewelsinto a set. - Initialize
answer = 0. - For each character
stoneinstones:- If
stoneis in the set, incrementanswer.
- If
- Return
answer.
Correctness
The set jewel_set contains exactly the characters from jewels.
For each character in stones, the algorithm checks whether that character appears in jewel_set.
If it does, then this stone is one of the jewel types, so the algorithm counts it.
If it does not, then this stone is not a jewel, so the algorithm correctly ignores it.
Since every stone is checked exactly once, the final count is exactly the number of stones that are jewels.
Complexity
Let:
m = len(jewels)
n = len(stones)| Metric | Value | Why |
|---|---|---|
| Time | O(m + n) | Build the set once, then scan all stones once |
| Space | O(m) | The set stores jewel types |
Implementation
class Solution:
def numJewelsInStones(self, jewels: str, stones: str) -> int:
jewel_set = set(jewels)
answer = 0
for stone in stones:
if stone in jewel_set:
answer += 1
return answerCode Explanation
We first create a set of jewel types:
jewel_set = set(jewels)For example:
jewels = "aA"becomes:
{"a", "A"}Then we count stones that appear in this set:
for stone in stones:
if stone in jewel_set:
answer += 1Because the comparison is case-sensitive, "a" and "A" remain separate characters.
Finally:
return answerreturns the total number of jewel stones.
Compact Python Version
The same logic can be written more compactly:
class Solution:
def numJewelsInStones(self, jewels: str, stones: str) -> int:
jewel_set = set(jewels)
return sum(stone in jewel_set for stone in stones)In Python, True counts as 1 and False counts as 0, so the sum counts how many membership checks are true.
Testing
def run_tests():
s = Solution()
assert s.numJewelsInStones("aA", "aAAbbbb") == 3
assert s.numJewelsInStones("z", "ZZ") == 0
assert s.numJewelsInStones("abc", "abcdef") == 3
assert s.numJewelsInStones("A", "aA") == 1
assert s.numJewelsInStones("xY", "xxYYYz") == 5
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
"aA", "aAAbbbb" | Main example with mixed case |
"z", "ZZ" | Confirms case sensitivity |
"abc", "abcdef" | Some stones are jewels |
"A", "aA" | Uppercase and lowercase differ |
"xY", "xxYYYz" | Counts repeated jewel stones |