First Non Repeating Character Using Hash in DSA Rust - Time & Space Complexity
We want to find how long it takes to find the first character that appears only once in a string using a hash map.
How does the time needed grow as the string gets longer?
Analyze the time complexity of the following code snippet.
use std::collections::HashMap;
fn first_non_repeating_char(s: &str) -> Option {
let mut counts = HashMap::new();
for ch in s.chars() {
*counts.entry(ch).or_insert(0) += 1;
}
for ch in s.chars() {
if counts[&ch] == 1 {
return Some(ch);
}
}
None
}
This code counts how many times each character appears, then finds the first character with count one.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Two loops over the string characters.
- How many times: Each loop runs once over all characters, so twice in total.
As the string gets longer, the number of steps grows roughly twice as fast because we scan the string two times.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 20 steps |
| 100 | About 200 steps |
| 1000 | About 2000 steps |
Pattern observation: The operations grow linearly with input size, doubling because of two passes.
Time Complexity: O(n)
This means the time to find the first non-repeating character grows directly in proportion to the string length.
[X] Wrong: "Because there are two loops, the time complexity is O(n²)."
[OK] Correct: Each loop runs separately over the string once, so their times add up, not multiply.
Understanding how to analyze multiple passes over data is a key skill. This example shows how two simple scans still keep the process efficient.
What if we used nested loops to check each character against all others? How would the time complexity change?
Practice
Solution
Step 1: Understand the role of hash map
A hash map stores each character as a key and its count as the value, allowing quick frequency lookup.Step 2: Why frequency matters
Knowing frequency helps identify which character appears only once, the first non-repeating character.Final Answer:
To store the frequency of each character for quick lookup -> Option BQuick Check:
Hash map = frequency storage [OK]
- Thinking hash map sorts characters
- Using hash map to reverse string
- Ignoring frequency counts
Solution
Step 1: Identify data structure for key-value pairs
HashMap stores characters as keys and their counts as values, perfect for frequency counting.Step 2: Why others don't fit
Vec<char> is just a list, String holds characters but no counts, Option<char> holds one optional char only.Final Answer:
HashMap -> Option AQuick Check:
HashMap = char to count map [OK]
- Using Vec instead of HashMap
- Confusing String with frequency map
- Using Option for multiple counts
use std::collections::HashMap;
fn first_non_repeating(s: &str) -> Option<char> {
let mut counts = HashMap::new();
for c in s.chars() {
*counts.entry(c).or_insert(0) += 1;
}
for c in s.chars() {
if counts[&c] == 1 {
return Some(c);
}
}
None
}
fn main() {
let result = first_non_repeating("swiss");
println!("{:?}", result);
}Solution
Step 1: Count character frequencies in "swiss"
's' appears 3 times, 'w' once, 'i' once.Step 2: Find first character with count 1 in original order
Order: 's'(3), 'w'(1), 'i'(1), 's'(3), 's'(3). First with count 1 is 'w'.Final Answer:
Some('w') -> Option DQuick Check:
First unique char = 'w' [OK]
- Picking first char without checking count
- Confusing 'i' and 'w' positions
- Ignoring multiple occurrences
fn first_non_repeating(s: &str) -> Option<char> {
let mut counts = std::collections::HashMap::new();
for c in s.chars() {
counts.insert(c, 1);
}
for c in s.chars() {
if counts[&c] == 1 {
return Some(c);
}
}
None
}Solution
Step 1: Analyze frequency counting loop
Using counts.insert(c, 1) overwrites previous counts, so frequency is always 1.Step 2: Effect on result
All characters appear counted as 1, so function returns first char incorrectly.Final Answer:
Using insert overwrites counts, so frequencies are incorrect -> Option AQuick Check:
insert overwrites count, use entry().or_insert() [OK]
- Using insert instead of entry().or_insert()
- Ignoring frequency updates
- Confusing chars and bytes
Solution
Step 1: Understand Unicode graphemes vs chars
Rust's chars() iterates Unicode scalar values, not grapheme clusters (user-perceived characters).Step 2: Use unicode-segmentation crate
This crate provides graphemes() iterator to correctly handle combined Unicode characters.Step 3: Count frequencies using graphemes
Counting graphemes ensures correct identification of non-repeating user-visible characters.Final Answer:
Use the unicode-segmentation crate to iterate over graphemes and count frequencies -> Option CQuick Check:
Unicode graphemes need special iterator [OK]
- Assuming chars() equals graphemes
- Counting bytes instead of characters
- Replacing Unicode with ASCII loses data
Ready to prove it?
Pass the test and this pattern moves unknown
