Remove Duplicate Characters in Java
Problem
Removing duplicate characters means keeping only the first occurrence of each character and dropping every repeat, while preserving the original order.
Given a string, remove every repeated character so each one appears only once, in its original order.
Java Program
import java.util.LinkedHashSet;
public class RemoveDuplicateChars {
public static void main(String[] args) {
String str = "programming";
LinkedHashSet<Character> seen = new LinkedHashSet<>();
// add() silently ignores duplicates, and insertion order is preserved
for (char c : str.toCharArray()) {
seen.add(c);
}
StringBuilder result = new StringBuilder();
for (char c : seen) {
result.append(c);
}
System.out.println(result.toString());
}
}Output
Core Logic
A LinkedHashSet already keeps only unique elements while preserving the order they were added in — exactly what deduplication needs.
- 1A
LinkedHashSet<Character>namedseentracks every distinct character encountered so far, in insertion order. - 2Adding a character that's already in the set has no effect —
Set.add()silently ignores duplicates. - 3After scanning the whole string once,
seenholds each character exactly once, in the order it first appeared. - 4A second loop walks
seenand appends each character to build the final result string.
"programming", the repeated 'r', 'm', and 'g' are silently dropped, leaving "progamin".Key Point: A plain HashSet would also remove duplicates, but wouldn't guarantee the original order — LinkedHashSet is the one that keeps 'first occurrence' order intact.
Why: Each character is visited once, and the LinkedHashSet plus the result buffer both hold up to n distinct characters.
Key Concepts
Approach 2: Manual Boolean Array
public class RemoveDuplicateCharsManual {
public static void main(String[] args) {
String str = "programming";
boolean[] seen = new boolean[128]; // covers standard ASCII characters
StringBuilder result = new StringBuilder();
// Append a character only the first time it's seen
for (char c : str.toCharArray()) {
if (!seen[c]) {
seen[c] = true;
result.append(c);
}
}
System.out.println(result.toString());
}
}
Output
Core Logic
For a known character set like ASCII, a fixed-size boolean array can track 'seen' status without the overhead of a Set of boxed Characters.
- 1A
boolean[128]array covers every ASCII character, indexed directly by a character's numeric code. - 2
if (!seen[c])checks whether this character has appeared before; if not, it's markedseen[c] = trueand appended to the result. - 3Characters already marked
seenare skipped entirely, without ever touching the result buffer. - 4The result
StringBuilderends up holding each distinct character exactly once, in the order first seen.
"programming", the second 'r' finds seen['r'] already true and is skipped, leaving "progamin" just like the Set-based version.Key Point: Indexing a primitive boolean array avoids the per-entry object overhead a HashSet or LinkedHashSet of boxed Characters would add — same O(n) asymptotic space, but a smaller constant factor.
Why: The result buffer still holds up to n distinct characters, but the fixed-size boolean array avoids the per-entry object overhead a Set of boxed Characters would add.
Key Concepts
Approach 3: Java 8
import java.util.stream.Collectors;
public class RemoveDuplicateCharsStream {
public static void main(String[] args) {
String str = "programming";
// distinct() keeps the first occurrence of each character, preserving order
String result = str.chars()
.distinct()
.mapToObj(c -> String.valueOf((char) c))
.collect(Collectors.joining());
System.out.println(result);
}
}
Output
Core Logic
IntStream.distinct() keeps only the first occurrence of each value while preserving encounter order — exactly the same guarantee a LinkedHashSet gives, expressed as a pipeline instead of an explicit loop.
- 1
str.chars()returns anIntStreamof the string's character codes, in order. - 2
.distinct()keeps only the first occurrence of each code, dropping every repeat, and preserves the original encounter order since the source stream is ordered. - 3
.mapToObj(c -> String.valueOf((char) c))converts each surviving code back into a single-characterString. - 4
.collect(Collectors.joining())concatenates those characters into the final result.
"programming", chars().distinct() keeps the first 'p', 'r', 'o', 'g', 'a', 'm', 'i', 'n' and drops the later repeats of 'r', 'm', and 'g', joining back into "progamin".Key Point: distinct() preserving encounter order isn't an accident of this particular case — it's a documented guarantee for ordered streams, which is exactly why it can stand in for the LinkedHashSet's insertion-order behavior.
Why: distinct() still has to track every distinct character seen so far internally, and the result string holds up to n distinct characters.