Introduction
Counting duplicate characters in a String is a common Java programming problem. It is also a popular coding interview question because it tests your understanding of Strings, loops, collections, and character frequency counting.
A duplicate character is a character that appears more than once in a String. For example, the String "programming" contains three duplicate characters: r, g, and m. Each of these characters appears twice.
Java provides several ways to identify duplicate characters and count their occurrences. You can use a traditional loop with a HashMap, the Stream API, or a Unicode-aware approach for Strings containing characters such as emojis.
In this tutorial, I will demonstrate three approaches with complete Java programs. I will also explain how each solution works, compare their performance, and discuss common mistakes to avoid.
1. Understanding the Duplicate Character Problem
Before writing the Java code, let's understand the expected result.
Consider the following String:
programming
The character frequencies are:
| Character | Frequency | Duplicate? |
|---|---|---|
| p | 1 | No |
| r | 2 | Yes |
| o | 1 | No |
| g | 2 | Yes |
| a | 1 | No |
| m | 2 | Yes |
| i | 1 | No |
| n | 1 | No |
The expected output is:
r = 2
g = 2
m = 2
Notice that the program should display only characters with a frequency greater than one.
2. Count Duplicate Characters in Java Using HashMap
Using a HashMap is one of the simplest and most efficient ways to count duplicate characters in a String.
A HashMap stores data as key-value pairs. For this problem, each character becomes a key, and its number of occurrences becomes the corresponding value.
For example, if the character r appears twice, the map stores r = 2.
In this example, I will use LinkedHashMap, which is a subclass of HashMap. It preserves the insertion order of characters, making the output easier to read and predict.
Java Program Using LinkedHashMap
import java.util.LinkedHashMap;
import java.util.Map;
public class DuplicateCharactersUsingMap {
public static void main(String[] args) {
String input = "programming";
Map<Character, Integer> characterCounts = new LinkedHashMap<>();
// Count how many times each character appears.
for (char character : input.toCharArray()) {
characterCounts.merge(character, 1, Integer::sum);
}
System.out.println("Duplicate characters:");
// Display only characters that appear more than once.
for (Map.Entry<Character, Integer> entry : characterCounts.entrySet()) {
if (entry.getValue() > 1) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}
}
Output
Duplicate characters:
r = 2
g = 2
m = 2
How Does This Program Work?
Let's understand the important parts of the program.
Step 1: Create a Map to Store Character Frequencies
Map<Character, Integer> characterCounts = new LinkedHashMap<>();
The map stores each character and its frequency.
Characterrepresents the character being counted.Integerrepresents how many times the character appears.LinkedHashMappreserves the order in which characters first appear.
Step 2: Loop Through the String
for (char character : input.toCharArray()) {
characterCounts.merge(character, 1, Integer::sum);
}
The toCharArray() method converts the String into an array of char values.
The enhanced for loop processes each character.
The merge() method updates the character count:
- If the character is not in the map, it adds the character with a count of
1. - If the character already exists,
Integer::sumincreases its count by1.
This approach avoids writing a separate condition to check whether a character already exists.
Step 3: Display Duplicate Characters
for (Map.Entry<Character, Integer> entry : characterCounts.entrySet()) {
if (entry.getValue() > 1) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
The entrySet() method provides access to the keys and values stored in the map.
The condition entry.getValue() > 1 ensures that only duplicate characters are displayed.
When should you use this approach?
Use this approach when you want a simple, readable solution that works well for ordinary text containing basic characters. It is a good choice for beginners and coding interviews.
Important: Java's char type represents a UTF-16 code unit. Some Unicode characters, including many emojis, require two char values. The third approach explains how to handle these characters correctly.
3. Count Duplicate Characters Using Java Streams
The Java Stream API provides another way to count duplicate characters. Streams let you process data through a sequence of operations such as mapping, grouping, and filtering.
This solution uses String.chars() to create a stream of character values. It then groups identical characters and counts their occurrences.
Although Streams can make collection processing concise, they may be harder to understand if you are new to Java. I recommend learning the map-based solution first.
Java Program Using Stream API
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class DuplicateCharactersUsingStreams {
public static void main(String[] args) {
String input = "programming";
// Group characters and count their occurrences.
Map<Character, Long> characterCounts = input.chars()
.mapToObj(value -> (char) value)
.collect(Collectors.groupingBy(
Function.identity(),
LinkedHashMap::new,
Collectors.counting()
));
System.out.println("Duplicate characters:");
// Print characters that occur more than once.
characterCounts.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.forEach(entry ->
System.out.println(
entry.getKey() + " = " + entry.getValue()
)
);
}
}
Output
Duplicate characters:
r = 2
g = 2
m = 2
Explanation
1. Convert the String into a Stream
input.chars()
The chars() method returns an IntStream containing the numeric values of the String's UTF-16 code units.
2. Convert Values into Character Objects
.mapToObj(value -> (char) value)
This operation converts each integer value into a Character object.
3. Group and Count Characters
.collect(Collectors.groupingBy(
Function.identity(),
LinkedHashMap::new,
Collectors.counting()
));
The groupingBy() collector groups identical characters.
Function.identity()uses each character as its grouping key.LinkedHashMap::newcreates a map that preserves the first-encountered character order in this sequential stream.Collectors.counting()counts the number of characters in each group.
The resulting map contains each character and its frequency.
4. Filter Duplicate Characters
.filter(entry -> entry.getValue() > 1)
The filter() operation keeps entries whose count is greater than one.
Finally, forEach() prints the duplicate characters and their frequencies.
When should you use this approach?
This approach is useful when you are already working with Java Streams or want to practice grouping and filtering operations.
For a simple character-counting program, the traditional map-based solution is usually easier to read.
4. Count Duplicate Unicode Characters in Java
The previous examples work well for common characters such as English letters, numbers, and punctuation.
However, Java Strings can also contain Unicode characters that require more than one UTF-16 code unit. Many emojis fall into this category.
To process these characters correctly, Java provides the codePoints() method.
Unlike chars(), which processes UTF-16 code units, codePoints() processes Unicode code points.
Let's use a String containing repeated emojis.
Java Program Using Unicode Code Points
import java.util.LinkedHashMap;
import java.util.Map;
public class DuplicateUnicodeCharacters {
public static void main(String[] args) {
String input = "Java 😀 Java 😀";
Map<Integer, Integer> characterCounts = new LinkedHashMap<>();
// Count complete Unicode code points.
input.codePoints().forEach(codePoint ->
characterCounts.merge(codePoint, 1, Integer::sum)
);
System.out.println("Duplicate characters:");
// Convert each Unicode code point back into a String.
characterCounts.forEach((codePoint, count) -> {
if (count > 1) {
String character = new String(Character.toChars(codePoint));
// Make whitespace visible in the output.
String displayCharacter = codePoint == ' '
? "[space]"
: character;
System.out.println(displayCharacter + " = " + count);
}
});
}
}
Output
Duplicate characters:
J = 2
a = 4
v = 2
[space] = 3
😀 = 2
How Does This Program Work?
The important difference is this statement:
input.codePoints()
It returns a stream of Unicode code points rather than individual UTF-16 code units.
The map uses Integer keys because Unicode code points are represented as integer values.
After counting the occurrences, the program converts each code point back into a String:
String character = new String(Character.toChars(codePoint));
The Character.toChars() method converts a Unicode code point into the corresponding UTF-16 representation.
The program also displays spaces as [space] so readers can identify them in the output.
When should you use this approach?
Use codePoints() when your application needs to handle supplementary Unicode characters such as many emojis or less common writing-system characters.
Keep in mind that a visible symbol can contain multiple code points. For example, some family emojis, skin-tone variations, and accented text sequences contain multiple code points. Counting complete user-perceived characters requires a more advanced text-segmentation approach.
5. Count Duplicate Characters Ignoring Case and Spaces
Sometimes you may want to count duplicate letters without treating uppercase and lowercase letters as different characters.
For example, consider the String "Java Programming".
By default, Java treats 'J' and 'j' as different characters. The space between words is also included in character counting.
To perform case-insensitive counting, convert the String to lowercase before processing it.
The following program also skips whitespace. It uses Locale.ROOT to make the case conversion independent of the computer's default locale.
Java Program
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
public class CaseInsensitiveDuplicateCharacters {
public static void main(String[] args) {
String input = "Java Programming";
// Normalize case using a locale-independent conversion.
String normalizedInput = input.toLowerCase(Locale.ROOT);
Map<Character, Integer> characterCounts = new LinkedHashMap<>();
for (char character : normalizedInput.toCharArray()) {
// Ignore spaces and other whitespace characters.
if (Character.isWhitespace(character)) {
continue;
}
characterCounts.merge(character, 1, Integer::sum);
}
System.out.println("Duplicate characters:");
characterCounts.forEach((character, count) -> {
if (count > 1) {
System.out.println(character + " = " + count);
}
});
}
}
Output
Duplicate characters:
a = 3
r = 2
g = 2
m = 2
The program converts "Java Programming" into "java programming" before counting characters.
It also ignores the space between the words.
This type of normalization is useful when analyzing user input, searching text, or comparing letter frequencies.
Note that this example demonstrates straightforward case-insensitive counting for ordinary text. More advanced international text processing may require Unicode case folding and normalization.
6. Comparing Different Approaches
Each approach has its advantages. The best choice depends on your requirements and the type of text you need to process.
| Approach | Main Advantage | Best Use Case |
|---|---|---|
| LinkedHashMap | Simple and readable | Beginners and coding interviews |
| Java Streams | Declarative grouping and filtering | Stream API practice and collection processing |
| Unicode Code Points | Handles supplementary Unicode characters | International text and emojis |
Time and Space Complexity
The map-based and Stream-based counting approaches typically process each character once.
- Time complexity: O(n) on average for hash-based counting, where n is the number of processed characters or code points.
- Space complexity: O(k), where k is the number of distinct characters or code points.
These estimates assume average constant-time hash map operations.
If the String contains only a small number of distinct characters, the map remains relatively small even when the input String is long.
The Stream API may create additional intermediate objects and has more processing overhead than a simple loop. For most small inputs, that difference is not important.
7. Common Mistakes When Counting Duplicate Characters
1. Printing Every Character Instead of Only Duplicates
Counting character frequencies and identifying duplicate characters are related but different operations.
If you want only duplicates, always check whether the count is greater than one:
if (count > 1) {
System.out.println(character + " = " + count);
}
2. Assuming HashMap Preserves Insertion Order
A regular HashMap does not guarantee iteration order.
If you want characters to appear in the order of their first occurrence, use LinkedHashMap.
3. Ignoring Case Sensitivity
Java treats uppercase and lowercase characters as different values.
For example, 'A' and 'a' are not equal.
If your requirements call for case-insensitive counting, normalize the String before counting.
4. Forgetting About Spaces
Spaces are characters too. A program will count them unless you explicitly exclude them.
Decide whether spaces, tabs, and other whitespace should be included based on the requirements.
5. Using char for Every Unicode Character
A Java char cannot represent every Unicode character individually.
Use String.codePoints() when you need to count Unicode code points accurately.
6. Not Handling Null or Empty Strings
An empty String has no duplicate characters. A null reference is different because it does not refer to a String object.
When building reusable methods, define how they should handle these cases. For example:
if (input == null) {
throw new IllegalArgumentException("Input cannot be null");
}
if (input.isEmpty()) {
System.out.println("No duplicate characters found.");
return;
}
For application code, returning an empty map for an empty String is often more useful than printing a message inside the counting method.
8. Frequently Asked Questions
How do you find duplicate characters in a Java String?
You can store each character and its frequency in a HashMap or LinkedHashMap. After counting the characters, display only entries whose frequency is greater than one.
Can I count duplicate characters without using a HashMap?
Yes. You can use nested loops or an array of counters for a known, limited character set. However, a map is usually easier to maintain and works well when the input contains different types of characters.
What is the difference between HashMap and LinkedHashMap?
Both store key-value pairs. A HashMap does not guarantee iteration order, while a LinkedHashMap preserves insertion order by default.
For this problem, a LinkedHashMap is useful when you want predictable output.
Are Java Strings case-sensitive?
Yes. Java distinguishes uppercase and lowercase characters. To ignore case, you can normalize the String using toLowerCase(Locale.ROOT) before counting.
Which approach is best for Java coding interviews?
The map-based approach is a good starting point. It is easy to explain and has O(n) average time complexity. You should also understand why LinkedHashMap is useful when output order matters.
Conclusion
In this tutorial, you learned how to find and count duplicate characters in a Java String using maps, Java Streams, and Unicode code points.
The LinkedHashMap approach is a practical choice for most beginner-level programs. It provides a straightforward way to count character frequencies and display duplicates in a predictable order.
The Stream API offers an alternative when you want to use functional-style collection processing. For text containing supplementary Unicode characters, String.codePoints() provides a more accurate way to count individual code points.
The key idea is the same in every approach: count how often each character appears, then select the characters whose frequency is greater than one.
As a next step, try modifying the program to find the first non-repeated character in a String. This is another useful Java coding interview problem that builds on the same frequency-counting technique.
Continue Learning with Java Guides
Want to take your programming skills to the next level? Explore my practical Udemy courses on Java, Spring Boot, Microservices, Full-Stack Development, Docker, and Generative AI. You can also follow my structured learning roadmaps to become a professional developer.
By Ramesh Fadatare - Udemy Instructor Partner
Comments
Post a Comment
Leave Comment