Queue based approach or first non-repeating character in a stream
Problem Statement
Given a stream of characters, find the first non-repeating character at each point in the stream. For each new character added, output the first character that appears only once in the stream so far. If no such character exists, output -1 or '#'.
Example:
Stream: "aabcbcd"
After 'a': First non-repeating = 'a'
After 'a': First non-repeating = -1 (both 'a's repeated)
After 'b': First non-repeating = 'b'
After 'c': First non-repeating = 'b'
After 'b': First non-repeating = 'c' (b repeated)
After 'c': First non-repeating = 'd' (c repeated)
After 'd': First non-repeating = 'd'
Output: "a" -> -1 -> "b" -> "b" -> "c" -> "d" -> "d"
Approach 1: Brute Force
Intuition
For each new character, scan through all characters seen so far and find the first one with count 1. Simple but inefficient for large streams.
Algorithm
- Maintain string of all characters seen
- Maintain frequency count for each character
- For each query:
- Scan from beginning of string
- Return first character with frequency 1
- Return -1 if none found
import java.util.*;
public class StreamCharsBruteForce {
private StringBuilder stream;
private int[] freq;
public StreamCharsBruteForce() {
stream = new StringBuilder();
freq = new int[26];
}
// O(n) per character
public char getFirstNonRepeating(char c) {
stream.append(c);
freq[c - 'a']++;
// Find first non-repeating
for (int i = 0; i < stream.length(); i++) {
char ch = stream.charAt(i);
if (freq[ch - 'a'] == 1) {
return ch;
}
}
return '#';
}
public static void main(String[] args) {
StreamCharsBruteForce sc = new StreamCharsBruteForce();
String stream = "aabcbcd";
System.out.println("Stream processing:");
for (char c : stream.toCharArray()) {
char result = sc.getFirstNonRepeating(c);
System.out.println("After '" + c + "': First non-repeating = " +
(result == '#' ? -1 : result));
}
}
}Complexity Analysis
- Time Complexity: O(n^2) overall - O(n) per character for n characters
- Space Complexity: O(n) for storing the stream
Approach 2: Optimal (Queue + Frequency Array)
Intuition
Use a queue to maintain the order of characters as they appear. The front of the queue is always a candidate for the first non-repeating character. When a character repeats, we don't need to remove it from queue immediately - just skip it when checking.
Algorithm
- Use queue to store characters in order
- Use frequency array to count occurrences
- For each new character:
- Increment frequency
- Add to queue
- Pop from front while front has frequency > 1
- Front of queue (if exists) is the answer
import java.util.*;
public class StreamChars {
private Queue<Character> queue;
private int[] freq;
public StreamChars() {
queue = new LinkedList<>();
freq = new int[26];
}
// O(1) amortized per character
public char getFirstNonRepeating(char c) {
freq[c - 'a']++;
queue.offer(c);
// Remove repeating characters from front
while (!queue.isEmpty() && freq[queue.peek() - 'a'] > 1) {
queue.poll();
}
if (queue.isEmpty()) {
return '#';
}
return queue.peek();
}
public static void main(String[] args) {
StreamChars sc = new StreamChars();
String stream = "aabcbcd";
System.out.println("Stream processing:");
for (char c : stream.toCharArray()) {
char result = sc.getFirstNonRepeating(c);
System.out.println("After '" + c + "': First non-repeating = " +
(result == '#' ? -1 : result));
}
}
}Complexity Analysis
- Time Complexity: O(n) overall - O(1) amortized per character (each char enters and exits queue at most once)
- Space Complexity: O(26) = O(1) for frequency array + O(n) worst case for queue
Approach 3: Using LinkedHashMap (Ordered Map)
Intuition
Use a data structure that maintains insertion order and allows O(1) deletion. In some languages, LinkedHashMap or OrderedDict provides this. Keep only non-repeating characters in the map.
Algorithm
- Use ordered map to store first occurrence of each character
- Use frequency array to track counts
- When character repeats, remove from ordered map
- First entry in ordered map is the answer
import java.util.*;
public class StreamCharsLinked {
private LinkedHashSet<Character> order; // Non-repeating chars in order
private Set<Character> repeated;
public StreamCharsLinked() {
order = new LinkedHashSet<>();
repeated = new HashSet<>();
}
// O(1) per character
public char getFirstNonRepeating(char c) {
if (repeated.contains(c)) {
// Already repeated, ignore
} else if (order.contains(c)) {
// Second occurrence - move to repeated
order.remove(c);
repeated.add(c);
} else {
// First occurrence
order.add(c);
}
if (order.isEmpty()) {
return '#';
}
return order.iterator().next();
}
public static void main(String[] args) {
StreamCharsLinked sc = new StreamCharsLinked();
String stream = "aabcbcd";
System.out.println("Stream processing:");
for (char c : stream.toCharArray()) {
char result = sc.getFirstNonRepeating(c);
System.out.println("After '" + c + "': First non-repeating = " +
(result == '#' ? -1 : result));
}
}
}Complexity Analysis
- Time Complexity: O(1) per character - all operations are O(1)
- Space Complexity: O(26) = O(1) for alphabet-sized storage
Visual Walkthrough
Stream: "aabcbcd"
After 'a':
Queue: [a]
Freq: {a:1}
First non-repeating: 'a'
After 'a':
Queue: [a, a] → pop 'a' (freq > 1) → []
Freq: {a:2}
First non-repeating: -1
After 'b':
Queue: [b]
Freq: {a:2, b:1}
First non-repeating: 'b'
After 'c':
Queue: [b, c]
Freq: {a:2, b:1, c:1}
First non-repeating: 'b'
After 'b':
Queue: [b, c] → pop 'b' (freq > 1) → [c]
Freq: {a:2, b:2, c:1}
First non-repeating: 'c'
After 'c':
Queue: [c] → pop 'c' (freq > 1) → []
Queue: [d] (after adding d)
Freq: {a:2, b:2, c:2}
Wait, we haven't added 'd' yet!
After 'c':
Queue: [c, c] → pop both → []
Freq: {a:2, b:2, c:2}
First non-repeating: -1
Actually, let me redo:
After 'c' (second c):
Queue: [c] → pop 'c' (freq > 1) → []
First non-repeating: -1 or wait for 'd'
After 'd':
Queue: [d]
Freq: {a:2, b:2, c:2, d:1}
First non-repeating: 'd'
Key Takeaways
- Queue for Order: Queue maintains the order in which non-repeating characters appeared
- Lazy Removal: Don't remove immediately when character repeats, check when needed
- Amortized Analysis: Each character enters and exits queue at most once → O(1) amortized
- Stream Processing: Pattern applicable to real-time data processing
- This is a classic interview problem testing queue and hash map usage
- Real-world use: Network packet analysis, log processing, real-time analytics