String Character Logic
Solve one Java logic problem at a time, then flip for the complete explanation and program.
Solve one Java logic problem at a time, then flip for the complete explanation and program.
Java Logic Development · Chapter 15 Companion Article
Fifteen character-level String problems — frequency counting, first/last repeated and non-repeated characters, duplicate and unique detection, most/least frequent characters, removing or replacing characters, sorting characters, separating alphabets from digits, and character occurrence positions — solved with loops, arrays, HashMap, LinkedHashMap, and HashSet. Followed by the recurring problem-solving patterns and edge cases every character-logic solution should be tested against.
String character problems test how well you can traverse a string, track character occurrences, preserve order, and build a new result. Most problems can be solved using loops, arrays, sets, maps, or StringBuilder.
These two operations are not the same:
Unless stated otherwise, the examples treat each Java char as one character. A Java char represents a UTF-16 code unit, so supplementary Unicode characters such as some emoji require code-point-based processing.
Count how many times each character occurs in a string.
Input:
banana
Output:
b -> 1
a -> 3
n -> 2
Store each character as a key and its occurrence count as the value.
LinkedHashMap is useful here because it keeps characters in their first-appearance order.
For:
banana
Processing:
| Character | Frequency after processing |
|---|---|
| b | b=1 |
| a | b=1, a=1 |
| n | b=1, a=1, n=1 |
| a | b=1, a=2, n=1 |
| n | b=1, a=2, n=2 |
| a | b=1, a=3, n=2 |
import java.util.LinkedHashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String str = "banana";
Map<Character, Integer> frequency = new LinkedHashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
for (Map.Entry<Character, Integer> entry : frequency.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
}
}
b -> 1
a -> 3
n -> 2
Every character is processed exactly once. The map keeps one entry per distinct character and updates its counter whenever that character appears again.
Here, n is the string length and k is the number of distinct characters.
Be ready to solve the same problem using:
A map is usually cleaner when the possible character set is not restricted.
Find the first character whose frequency is exactly one.
Input:
swiss
Output:
w
First calculate all frequencies. Then scan the original string again.
A single traversal cannot always tell whether the current character will appear again later.
Use two passes:
For:
swiss
Frequencies:
Original order:
Answer:
w
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String str = "swiss";
Map<Character, Integer> frequency = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
Character result = null;
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (frequency.get(ch) == 1) {
result = ch;
break;
}
}
System.out.println(result == null ? "None" : result);
}
}
w
The frequency map tells whether a character occurs once or multiple times. Traversing the original string preserves the correct left-to-right order.
For:
aabbcc
No character occurs exactly once, so the result is:
None
Iterating through an ordinary HashMap to find the "first" character. HashMap does not guarantee original string order.
The important point is not only counting frequency. You must also preserve the original character order when finding the first valid character.
Find the first character encountered for the second time while scanning from left to right.
Input:
programming
Output:
r
Store characters that have already been seen.
This avoids calculating every character frequency.
For:
programming
Traversal:
First repeated character:
r
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
String str = "programming";
Set<Character> seen = new HashSet<>();
Character repeated = null;
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (!seen.add(ch)) {
repeated = ch;
break;
}
}
System.out.println(repeated == null ? "None" : repeated);
}
}
r
A set contains only characters already visited. Therefore, finding a character already inside the set proves that it has occurred previously.
For:
java
The second a is the first repeated occurrence, so the result is:
a
For:
code
The result is:
None
Clarify the definition of "first repeated."
It can mean:
Some inputs produce different answers under these definitions.
Find the last character that occurs exactly once.
Input:
swiss
Output:
i
Count frequencies first, then search from the end.
Searching backward directly finds the last valid character.
Frequencies of swiss:
Reverse traversal:
Answer:
i
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String str = "swiss";
Map<Character, Integer> frequency = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
Character result = null;
for (int i = str.length() - 1; i >= 0; i--) {
char ch = str.charAt(i);
if (frequency.get(ch) == 1) {
result = ch;
break;
}
}
System.out.println(result == null ? "None" : result);
}
}
i
Frequency identifies valid non-repeated characters, while reverse traversal changes the selection rule from first unique to last unique.
Finding every unique character correctly but returning the first one instead of the last.
If you already have a first-non-repeated solution, this problem mainly tests whether you can modify traversal direction without unnecessarily redesigning the algorithm.
Print characters that occur more than once. Each duplicate character should be printed only once.
Input:
programming
Output:
r g m
Count all characters and select those whose frequency is greater than 1.
LinkedHashMap ensures duplicate characters are shown according to their first appearance.
For programming:
| Character | Frequency |
|---|---|
| p | 1 |
| r | 2 |
| o | 1 |
| g | 2 |
| a | 1 |
| m | 2 |
| i | 1 |
| n | 1 |
Duplicates:
r g m
import java.util.LinkedHashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String str = "programming";
Map<Character, Integer> frequency = new LinkedHashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
StringBuilder result = new StringBuilder();
for (Map.Entry<Character, Integer> entry : frequency.entrySet()) {
if (entry.getValue() > 1) {
if (result.length() > 0) {
result.append(" ");
}
result.append(entry.getKey());
}
}
System.out.println(result);
}
}
r g m
Frequency greater than one is the exact condition for a duplicate. Since the map contains only one entry per distinct character, each duplicate is printed once.
Printing a duplicate whenever it is encountered again. For a character occurring four times, that approach may print the same character three times.
State whether the expected output needs:
They are different problems.
Find all characters that occur exactly once.
Input:
programming
Output:
p o a i n
A unique character has frequency exactly 1.
For programming:
Repeated characters:
Characters occurring once:
import java.util.LinkedHashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String str = "programming";
Map<Character, Integer> frequency = new LinkedHashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
StringBuilder result = new StringBuilder();
for (Map.Entry<Character, Integer> entry : frequency.entrySet()) {
if (entry.getValue() == 1) {
if (result.length() > 0) {
result.append(" ");
}
result.append(entry.getKey());
}
}
System.out.println(result);
}
}
p o a i n
A character is unique only when its total number of occurrences is one. Checking frequency after processing the complete string avoids incorrectly classifying a character before later occurrences are known.
Confusing "unique characters" with "distinct characters."
For:
banana
Distinct characters are:
b a n
Unique characters are only:
b
Always clarify the terminology. In interview questions, "unique" normally means frequency 1, while "distinct" normally means no duplicate values in the output.
Find the character having the highest frequency.
Input:
banana
Output:
a -> 3
Count frequencies and maintain the largest count seen.
Using greater-than instead of greater-than-or-equal keeps the first character when multiple characters have the same maximum frequency.
Frequency table:
Comparisons:
Result:
a -> 3
import java.util.LinkedHashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String str = "banana";
Map<Character, Integer> frequency = new LinkedHashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
Character mostFrequent = null;
int maxFrequency = 0;
for (Map.Entry<Character, Integer> entry : frequency.entrySet()) {
if (entry.getValue() > maxFrequency) {
maxFrequency = entry.getValue();
mostFrequent = entry.getKey();
}
}
if (mostFrequent == null) {
System.out.println("None");
} else {
System.out.println(mostFrequent + " -> " + maxFrequency);
}
}
}
a -> 3
Every distinct character has one final frequency. Comparing those values is sufficient to identify the maximum.
For:
aabb
Both a and b occur twice.
This implementation returns:
a -> 2
because a appears first.
Using greater-than-or-equal when the requirement says to return the first character in case of a tie.
Ask how ties should be handled:
Find the character having the smallest occurrence count.
Input:
banana
Output:
b -> 1
Start with a very large minimum value and reduce it whenever a smaller frequency is found.
Frequency:
Processing:
Result:
b -> 1
import java.util.LinkedHashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String str = "banana";
Map<Character, Integer> frequency = new LinkedHashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
Character leastFrequent = null;
int minFrequency = Integer.MAX_VALUE;
for (Map.Entry<Character, Integer> entry : frequency.entrySet()) {
if (entry.getValue() < minFrequency) {
minFrequency = entry.getValue();
leastFrequent = entry.getKey();
}
}
if (leastFrequent == null) {
System.out.println("None");
} else {
System.out.println(leastFrequent + " -> " + minFrequency);
}
}
}
b -> 1
The algorithm compares the final frequency of every distinct character and keeps the smallest value found.
If every character occurs once, every character has the same minimum frequency. This implementation returns the first character.
Initializing minimum frequency to 0. Character frequencies are positive, so no valid count will normally be smaller than 0.
Integer.MAX_VALUE is a common and safe initialization when searching for a minimum value.
Remove additional occurrences of characters while keeping the first occurrence.
Input:
programming
Output:
progamin
Append a character only when it has not appeared before.
For programming:
| Character | Already Seen? | Result |
|---|---|---|
| p | No | p |
| r | No | pr |
| o | No | pro |
| g | No | prog |
| r | Yes | prog |
| a | No | proga |
| m | No | progam |
| m | Yes | progam |
| i | No | progami |
| n | No | progamin |
| g | Yes | progamin |
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
String str = "programming";
Set<Character> seen = new HashSet<>();
StringBuilder result = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (seen.add(ch)) {
result.append(ch);
}
}
System.out.println(result);
}
}
progamin
A set accepts each character only once. The string is scanned in original order, so the first occurrence is preserved and later copies are skipped.
Input:
aaaa
Output:
a
Input:
java
Output:
jav
Sorting the string before removing duplicates. Sorting changes the original character order.
This problem can be solved without a frequency map because you only need to know whether a character has already appeared.
Remove every character that occurs more than once. Keep only characters whose total frequency is exactly one.
Input:
programming
Output:
poain
A set alone is not enough because you must know the final frequency before deciding whether to keep a character.
Repeated characters are completely removed, including their first occurrence.
For programming:
Frequencies:
Keep:
Result:
poain
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String str = "programming";
Map<Character, Integer> frequency = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (frequency.get(ch) == 1) {
result.append(ch);
}
}
System.out.println(result);
}
}
poain
A character should remain only when its total count is one. Calculating frequencies before constructing the result provides that information.
For:
banana
Remove duplicate characters:
ban
Remove repeated characters:
b
The first operation keeps one copy. The second removes every character that appeared multiple times.
Using only a HashSet and assuming it can determine total character frequency.
The wording "remove duplicates" and "remove repeated characters" is sometimes used inconsistently. Confirm the expected output before coding.
Replace every occurrence of one character with another character.
String:
banana
Replace:
a with o
Output:
bonono
Traverse every position and decide whether to append the replacement or original character.
For each character:
String objects are immutable in Java, so StringBuilder is suitable for constructing the modified string.
Input:
banana
Target:
a
Replacement:
o
Processing:
Result:
bonono
public class Main {
public static void main(String[] args) {
String str = "banana";
char target = 'a';
char replacement = 'o';
StringBuilder result = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (ch == target) {
result.append(replacement);
} else {
result.append(ch);
}
}
System.out.println(result);
}
}
bonono
Every character is copied into a new result. Only positions containing the target character receive a different value.
The output itself may contain n characters.
Java also provides String.replace() when manual logic is not required.
Conceptually:
str.replace('a', 'o')
For logic-development exercises, manual traversal better demonstrates the underlying operation.
Expecting the original String object to change. Java strings are immutable.
Be clear whether the requirement is to replace:
Remove every occurrence of a specified character from a string.
String:
banana
Remove:
a
Output:
bnn
Build a result containing every character except the target.
Input:
banana
Remove:
a
Processing:
Result:
bnn
public class Main {
public static void main(String[] args) {
String str = "banana";
char remove = 'a';
StringBuilder result = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (ch != remove) {
result.append(ch);
}
}
System.out.println(result);
}
}
bnn
Removing a character from an immutable String is effectively the same as constructing another string without the unwanted positions.
If the character does not exist, the original text remains unchanged.
If every character matches the target, the result is an empty string.
Removing characters while increasing indexes in a mutable sequence without considering that indexes shift after deletion.
Building a new result avoids that issue.
For a single-pass filter operation, StringBuilder is usually simpler than repeatedly creating new String objects.
Arrange string characters in ascending order.
Input:
logic
Output:
cgilo
Convert the String to a char array so individual positions can be swapped.
This example manually sorts characters to demonstrate the sorting logic.
Character comparison is based on Java's numeric char values.
Input:
logic
Initial:
l o g i c
After comparisons and swaps, the ordered characters become:
c g i l o
Result:
cgilo
public class Main {
public static void main(String[] args) {
String str = "logic";
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length - 1; i++) {
for (int j = i + 1; j < chars.length; j++) {
if (chars[i] > chars[j]) {
char temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
}
}
System.out.println(new String(chars));
}
}
cgilo
After each outer-loop iteration, the smallest remaining character is placed at the current position. Eventually every position contains the correct ascending character.
For this manual sorting algorithm:
When implementing application code rather than practicing sorting logic, Java's standard library is preferable.
Typical approach:
char[] chars = str.toCharArray();
Arrays.sort(chars);
Uppercase and lowercase letters do not naturally sort as though case were ignored.
For example, character values place uppercase Latin letters before lowercase Latin letters.
Therefore, define the required ordering when case-insensitive sorting is expected.
Assuming alphabetical order and raw char-value order are always identical for every language and Unicode character.
If built-in sorting is prohibited, explain which sorting algorithm you are using and its time complexity.
Separate letters and numeric digits from a mixed string.
Input:
Java17SE21
Output:
Alphabets: JavaSE
Digits: 1721
Character.isLetter() and Character.isDigit() clearly express the required classification.
For:
Java17SE21
Processing:
| Character | Type | Destination |
|---|---|---|
| J | Letter | Alphabets |
| a | Letter | Alphabets |
| v | Letter | Alphabets |
| a | Letter | Alphabets |
| 1 | Digit | Digits |
| 7 | Digit | Digits |
| S | Letter | Alphabets |
| E | Letter | Alphabets |
| 2 | Digit | Digits |
| 1 | Digit | Digits |
Results:
JavaSE
1721
public class Main {
public static void main(String[] args) {
String str = "Java17SE21";
StringBuilder alphabets = new StringBuilder();
StringBuilder digits = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (Character.isLetter(ch)) {
alphabets.append(ch);
} else if (Character.isDigit(ch)) {
digits.append(ch);
}
}
System.out.println("Alphabets: " + alphabets);
System.out.println("Digits: " + digits);
}
}
Alphabets: JavaSE
Digits: 1721
Each character is independently classified. Letters and digits are appended to different result builders while their original relative order remains unchanged.
For:
Java@17#SE
The current program ignores @ and #.
If special characters must also be collected, use a third StringBuilder inside an else block.
For English letters and ASCII digits, conditions can also be written manually:
ch >= 'A' && ch <= 'Z'
ch >= 'a' && ch <= 'z'
ch >= '0' && ch <= '9'
Character.isLetter() and Character.isDigit() are clearer and support more Unicode characters.
Using only lowercase ranges and accidentally ignoring uppercase letters.
Mention whether the problem expects only English A-Z/a-z characters or broader Unicode letter classification.
Find every index at which a specified character occurs.
String:
banana
Character:
a
Output:
[1, 3, 5]
Compare the target character with every position and store matching indexes.
Input:
banana
Target:
a
Traversal:
| Index | Character | Match? |
|---|---|---|
| 0 | b | No |
| 1 | a | Yes |
| 2 | n | No |
| 3 | a | Yes |
| 4 | n | No |
| 5 | a | Yes |
Positions:
1, 3, 5
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
String str = "banana";
char target = 'a';
List<Integer> positions = new ArrayList<>();
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == target) {
positions.add(i);
}
}
System.out.println(positions);
}
}
[1, 3, 5]
The loop checks every valid string index, so no occurrence can be missed. Matching indexes are stored rather than the characters themselves.
Here, m is the number of matching occurrences.
If the target character does not occur, the list remains empty:
[]
Java uses zero-based indexes.
For:
banana
The character a occurs at Java indexes:
1, 3, 5
If a problem asks for human-readable positions starting from 1, store:
i + 1
instead.
Printing i + 1 when the question specifically asks for Java indexes.
Clarify whether the interviewer wants:
| Problem | Best Basic Technique | Main Condition |
|---|---|---|
| Character Frequency | Map | Increment count |
| First Non-Repeated | Frequency + forward scan | count == 1 |
| First Repeated | Set | Character already seen |
| Last Non-Repeated | Frequency + reverse scan | count == 1 |
| Duplicate Characters | Frequency map | count > 1 |
| Unique Characters | Frequency map | count == 1 |
| Most Frequent Character | Frequency + maximum | count > max |
| Least Frequent Character | Frequency + minimum | count < min |
| Remove Duplicate Characters | Set | Keep first occurrence |
| Remove Repeated Characters | Frequency map | Keep count == 1 |
| Replace Character | StringBuilder | ch == target |
| Remove Character | StringBuilder | ch != target |
| Sort Characters | char[] + sorting | Compare characters |
| Separate Alphabets and Digits | Character methods | isLetter / isDigit |
| Character Occurrence Positions | Index traversal | ch == target |
Use when the answer depends on the total number of occurrences.
Typical problems:
Basic pattern:
Map<Character, Integer> frequency = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
Use when only previous occurrence information is required.
Typical problems:
Basic idea:
Set<Character> seen = new HashSet<>();
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
if (seen.add(ch)) {
// First occurrence
} else {
// Already appeared
}
}
Use StringBuilder when creating a transformed string.
Typical problems:
Repeated String concatenation inside large loops can create unnecessary temporary String objects because String is immutable.
Traversal direction can simplify positional requirements.
Use forward traversal for:
Use reverse traversal for:
Choose the collection based on the requirement.
Useful when:
Useful when:
Useful when:
String character programs should be tested with meaningful boundary cases.
""
No character is available for frequency-based selection problems.
"a"
The character is:
"aaaa"
Frequency:
a = 4
There is no non-repeated character.
"javax"
Every character occurs once.
For:
"java code"
A space is also a char and will be processed unless explicitly ignored.
To ignore spaces:
if (ch == ' ') {
continue;
}
Java character comparison is case-sensitive.
Therefore:
'A' != 'a'
For:
JavaJAVA
uppercase and lowercase versions are different characters unless the input is normalized first.
For case-insensitive logic, one possible preprocessing step is:
str = str.toLowerCase();
Only do this when the problem explicitly says case should be ignored.
After understanding the basic problems, useful variations include: