Java ProgramsStringsRemove Duplicate Characters

Remove Duplicate Characters in Java

intermediate·  Strings  ·  String Manipulation

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.

Input
programming
Output
progamin

Java Program

Java
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

progamin

Core Logic

A LinkedHashSet already keeps only unique elements while preserving the order they were added in — exactly what deduplication needs.

How It Works
  1. 1A LinkedHashSet<Character> named seen tracks every distinct character encountered so far, in insertion order.
  2. 2Adding a character that's already in the set has no effect — Set.add() silently ignores duplicates.
  3. 3After scanning the whole string once, seen holds each character exactly once, in the order it first appeared.
  4. 4A second loop walks seen and appends each character to build the final result string.
For "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.

Complexity
Time Complexity: O(n)Space Complexity: O(n)

Why: Each character is visited once, and the LinkedHashSet plus the result buffer both hold up to n distinct characters.

Key Concepts

LinkedHashSetinsertion orderfor-each loop

Approach 2: Manual Boolean Array

Java
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

progamin

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.

How It Works
  1. 1A boolean[128] array covers every ASCII character, indexed directly by a character's numeric code.
  2. 2if (!seen[c]) checks whether this character has appeared before; if not, it's marked seen[c] = true and appended to the result.
  3. 3Characters already marked seen are skipped entirely, without ever touching the result buffer.
  4. 4The result StringBuilder ends up holding each distinct character exactly once, in the order first seen.
Scanning "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.

Complexity
Time Complexity: O(n)Space Complexity: O(n)

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

boolean arrayASCII indexingStringBuilder

Approach 3: Java 8

Java
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

progamin

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.

How It Works
  1. 1str.chars() returns an IntStream of the string's character codes, in order.
  2. 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. 3.mapToObj(c -> String.valueOf((char) c)) converts each surviving code back into a single-character String.
  4. 4.collect(Collectors.joining()) concatenates those characters into the final result.
For "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.

Complexity
Time Complexity: O(n)Space Complexity: O(n)

Why: distinct() still has to track every distinct character seen so far internally, and the result string holds up to n distinct characters.

Key Concepts

Streamchars()distinct()

Related Programs