771. Jewels and Stones
Problem Description
You are given two strings: jewels
and stones
.
The string jewels
represents the types of stones that are considered jewels. Each character in this string represents a unique type of jewel.
The string stones
represents all the stones you have. Each character in this string represents one stone.
Your task is to count how many of your stones are also jewels. In other words, you need to find how many characters in stones
also appear in jewels
.
An important detail is that letters are case-sensitive. This means "a"
and "A"
are treated as completely different types of stones.
For example:
- If
jewels = "aA"
andstones = "aAAbbbb"
, the answer would be3
because you have three stones ("a"
,"A"
,"A"
) that match the jewel types defined injewels
. - The character
"a"
appears once instones
and is a jewel - The character
"A"
appears twice instones
and is a jewel - The character
"b"
appears four times instones
but is not a jewel
The solution uses a set to store all jewel types for O(1) lookup time, then iterates through each stone to check if it's a jewel, counting the matches.
Intuition
The core insight here is recognizing that this is fundamentally a membership checking problem. We need to repeatedly ask: "Is this stone a jewel?" for every stone we have.
When we need to check membership multiple times, a hash set immediately comes to mind because it provides O(1) lookup time. If we were to check each stone against the jewels
string directly using something like stone in jewels
, we'd be doing a linear search for each stone, resulting in O(n*m) time complexity where n is the length of stones
and m is the length of jewels
.
By converting jewels
to a set first, we transform the problem:
- Build a set of all jewel types:
s = set(jewels)
- This takes O(m) time - For each stone, check if it exists in our jewel set:
c in s
- Each check is O(1) - Count all the stones that are jewels
The beauty of the solution sum(c in s for c in stones)
is that it leverages Python's generator expression with the sum()
function. The expression c in s
evaluates to True
(which equals 1) when the stone is a jewel, and False
(which equals 0) when it's not. Summing these boolean values gives us the total count directly.
This approach scales well because no matter how many jewel types we have, checking if a stone is a jewel remains a constant-time operation thanks to the hash set's properties.
Solution Approach
The solution uses a Hash Table (implemented as a Python set) to efficiently count jewels among the stones.
Step-by-step implementation:
-
Create a hash set of jewels:
s = set(jewels)
This converts the
jewels
string into a set, where each character becomes a unique element. For example, ifjewels = "aA"
, thens = {'a', 'A'}
. This preprocessing step takes O(m) time where m is the length ofjewels
. -
Count stones that are jewels:
return sum(c in s for c in stones)
This line does several things:
for c in stones
: Iterates through each character (stone) in thestones
stringc in s
: Checks if the current stonec
exists in our jewel sets
. This returnsTrue
orFalse
- The generator expression
(c in s for c in stones)
produces a sequence of boolean values sum()
: Adds up all the boolean values, whereTrue
counts as 1 andFalse
counts as 0
Why use a set instead of the original string?
Checking membership in a set is O(1) on average, while checking membership in a string is O(m). Since we need to perform this check for every stone, using a set reduces our overall time complexity from O(n*m) to O(n+m).
Time Complexity: O(n + m), where n is the length of stones
and m is the length of jewels
- O(m) to build the set
- O(n) to iterate through all stones with O(1) lookup for each
Space Complexity: O(m) for storing the jewel types in the set
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with jewels = "aA"
and stones = "aAAbbbb"
.
Step 1: Create a hash set of jewels
- We convert
jewels = "aA"
into a set s = set("aA")
results ins = {'a', 'A'}
- Now we have O(1) lookup for any jewel type
Step 2: Iterate through each stone and check if it's a jewel
Let's trace through each character in stones = "aAAbbbb"
:
Stone (index) | Character | Is it in set s? | Boolean value | Running count |
---|---|---|---|---|
stones[0] | 'a' | 'a' in {'a', 'A'} = True | 1 | 1 |
stones[1] | 'A' | 'A' in {'a', 'A'} = True | 1 | 2 |
stones[2] | 'A' | 'A' in {'a', 'A'} = True | 1 | 3 |
stones[3] | 'b' | 'b' in {'a', 'A'} = False | 0 | 3 |
stones[4] | 'b' | 'b' in {'a', 'A'} = False | 0 | 3 |
stones[5] | 'b' | 'b' in {'a', 'A'} = False | 0 | 3 |
stones[6] | 'b' | 'b' in {'a', 'A'} = False | 0 | 3 |
Step 3: Sum the results
- The generator expression
(c in s for c in stones)
produces:True, True, True, False, False, False, False
- When passed to
sum()
, these boolean values are treated as:1, 1, 1, 0, 0, 0, 0
- Final sum:
1 + 1 + 1 + 0 + 0 + 0 + 0 = 3
Therefore, we have 3 stones that are jewels.
The complete solution in one line: return sum(c in set(jewels) for c in stones)
Solution Implementation
1class Solution:
2 def numJewelsInStones(self, jewels: str, stones: str) -> int:
3 # Create a set of jewel characters for O(1) lookup
4 jewel_set = set(jewels)
5
6 # Count how many stones are also jewels
7 # For each character in stones, check if it exists in jewel_set
8 # Sum up the boolean results (True=1, False=0)
9 return sum(char in jewel_set for char in stones)
10
1class Solution {
2 public int numJewelsInStones(String jewels, String stones) {
3 // Create a lookup table for all ASCII characters (128 total)
4 // Index represents the character's ASCII value
5 // Value will be 1 if character is a jewel, 0 otherwise
6 int[] isJewel = new int[128];
7
8 // Mark each jewel character in the lookup table
9 for (char jewel : jewels.toCharArray()) {
10 isJewel[jewel] = 1;
11 }
12
13 // Count how many stones are also jewels
14 int jewelCount = 0;
15 for (char stone : stones.toCharArray()) {
16 // Add 1 to count if current stone is a jewel (isJewel[stone] = 1)
17 // Add 0 to count if current stone is not a jewel (isJewel[stone] = 0)
18 jewelCount += isJewel[stone];
19 }
20
21 return jewelCount;
22 }
23}
24
1class Solution {
2public:
3 int numJewelsInStones(string jewels, string stones) {
4 // Create a lookup table for all ASCII characters (0-127)
5 // isJewel[i] = 1 if character i is a jewel, 0 otherwise
6 int isJewel[128] = {0};
7
8 // Mark all jewel characters in the lookup table
9 for (char jewel : jewels) {
10 isJewel[jewel] = 1;
11 }
12
13 // Count how many stones are also jewels
14 int jewelCount = 0;
15 for (char stone : stones) {
16 // Add 1 to count if current stone is a jewel, 0 otherwise
17 jewelCount += isJewel[stone];
18 }
19
20 return jewelCount;
21 }
22};
23
1/**
2 * Counts how many stones are also jewels
3 * @param jewels - String containing types of jewels (each character is a jewel type)
4 * @param stones - String containing stones to check (each character is a stone)
5 * @returns Number of stones that are also jewels
6 */
7function numJewelsInStones(jewels: string, stones: string): number {
8 // Create a set of jewel types for O(1) lookup
9 const jewelSet = new Set<string>([...jewels]);
10
11 // Counter for stones that are jewels
12 let jewelCount = 0;
13
14 // Iterate through each stone and check if it's a jewel
15 for (const stone of stones) {
16 if (jewelSet.has(stone)) {
17 jewelCount++;
18 }
19 }
20
21 return jewelCount;
22}
23
Time and Space Complexity
The time complexity is O(m + n)
, where m
is the length of the string jewels
and n
is the length of the string stones
. This breaks down as:
- Creating the set from
jewels
takesO(m)
time - Iterating through each character in
stones
takesO(n)
time - The membership check
c in s
for a set isO(1)
on average - Therefore, the total time complexity is
O(m) + O(n) = O(m + n)
The space complexity is O(m)
or alternatively O(|Σ|)
, where |Σ|
represents the size of the character set. The analysis is:
- The set
s
stores at mostm
unique characters fromjewels
, requiringO(m)
space - Since the problem constrains the input to uppercase and lowercase English letters, the set can contain at most 52 distinct characters, making the space complexity bounded by
O(|Σ|)
where|Σ| = 52
- The generator expression
sum(c in s for c in stones)
usesO(1)
additional space - Therefore, the space complexity is
O(min(m, |Σ|))
, which simplifies toO(|Σ|)
in the worst case
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting Case Sensitivity
A frequent mistake is assuming that jewels and stones are case-insensitive, leading to incorrect counts.
Incorrect approach:
def numJewelsInStones(self, jewels: str, stones: str) -> int:
# Wrong: Converting to lowercase loses case distinction
jewel_set = set(jewels.lower())
return sum(char.lower() in jewel_set for char in stones)
Why it's wrong: If jewels = "aA"
and stones = "aAAbbbb"
, this would incorrectly count all three A's and the single 'a' as the same type, potentially giving wrong results when the problem explicitly states that 'a' and 'A' are different jewel types.
Correct approach: Keep the original case as shown in the provided solution.
2. Using String Membership Instead of Set
While functionally correct, using the string directly for membership checking is inefficient.
Inefficient approach:
def numJewelsInStones(self, jewels: str, stones: str) -> int:
# Works but inefficient: O(n*m) time complexity
return sum(char in jewels for char in stones)
Why it's inefficient: Checking if a character exists in a string requires scanning through the string (O(m) operation). Doing this for each stone results in O(n*m) complexity instead of the optimal O(n+m).
3. Assuming Unique Characters in Jewels
While the problem states each character in jewels
represents a unique type, some might write unnecessary code to handle duplicates.
Overcomplicated approach:
def numJewelsInStones(self, jewels: str, stones: str) -> int:
# Unnecessary: set() already handles duplicates
jewel_set = set()
for jewel in jewels:
if jewel not in jewel_set:
jewel_set.add(jewel)
return sum(char in jewel_set for char in stones)
Why it's unnecessary: The set()
constructor automatically handles duplicates, making the manual checking redundant. Even if jewels
had duplicates (though the problem says it won't), set(jewels)
would handle it correctly.
Which of the following is a min heap?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!