Lesson 21Phase 2: Objects
String practice problems
Reversing, counting, checking palindromes, and other classic string exercises solved step by step.
By BasithUpdated 60 min
- string traversal
- reversal
- palindrome
- character counting
- comparison
- search
You have the tools now: a string knows its length(), it hands you any character with charAt, and a loop can walk it from end to end. This lesson is one long workout. It takes five classic string problems, the kind that turn up in coding interviews and in everyday programs, and solves each one slowly, with the index walking the string in front of you.
- Walk a string forwards and backwards with a
forloop andcharAt. - Build a new string one character at a time, with
+for short text and aStringBuilderfor long text. - Solve reverse, vowel count, palindrome, character count and first non-repeating character by hand.
- Spot the counting, two-pointer and nested-search patterns when they come up again.
length(), charAt, substring, equals, StringBuilder, and the fact that a string is immutable, so you never change one in place; you build a new one. You also need loops, including break, and the idea from variables and types that a char is a small number underneath. There are no new language features on this page, only practice.i walks from 0 up to s.length() - 1, pulling out one character at a time with s.charAt(i). Once that rhythm is in your fingers, much of string programming comes down to deciding what to do with each character as it goes by.Problem 1: reverse a string#
Given a string such as "java", make a new string with its characters in the opposite order: "avaj".
A string is immutable, so you cannot shuffle "java" in place. Instead, start with an empty string and add characters to it. The trick is the direction you read in. Walk the original from its last index down to its first, adding each character as you go, and the new string comes out reversed.
public class Main { public static void main(String[] args) { String s = "java"; String reversed = ""; for (int i = s.length() - 1; i >= 0; i--) { reversed = reversed + s.charAt(i); } System.out.println(reversed); }}Press Run and watch i count down: 3, 2, 1, 0. Each round takes one character from the end and adds it to reversed, which grows as "a", then "av", then "ava", then "avaj".
The line that does the work
reversed = reversed + s.charAt(i). Read it right to left. s.charAt(i) is the character at the current index. The + joins it to whatever reversed already holds, and the assignment stores that longer string back in reversed. Because you read s from the end, its last character lands first and its first character lands last.
for loop does not have to go up. i-- walks it down, and i >= 0 keeps it going while the index is still a real position. Same loop, opposite direction.Reverse.java, someone writes the condition as i > 0 instead of i >= 0. What does the program print now?For long text, a StringBuilder
Adding to a string with + in a loop makes a new string every round, which you saw is fine for a short word and slow for long text. The same loop with the StringBuilder from the strings lesson appends to one builder instead, and the builder can also reverse itself in one call.
public class Main { public static void main(String[] args) { String s = "java"; StringBuilder reversed = new StringBuilder(); for (int i = s.length() - 1; i >= 0; i--) { reversed.append(s.charAt(i)); } System.out.println(reversed); System.out.println(new StringBuilder(s).reverse()); }}Both lines print avaj. Step through the loop and watch the builder in the memory view gain one character per round. The rest of this page builds results with +, because the words are short and the steps are easier to follow, but the habit to carry into real programs is the builder.
Problem 2: count the vowels#
Given a word, count how many of its letters are vowels (a, e, i, o, u). For "Programming" the answer is 3.
This time walk the string forwards. Keep a counter that starts at 0. For each character, ask “is this one of the five vowels?”, and if it is, add one to the counter. Lower-case the whole string first, so that a capital A counts the same as a small a.
public class Main { public static void main(String[] args) { String s = "Programming"; s = s.toLowerCase(); int count = 0; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { count = count + 1; } } System.out.println("Vowels: " + count); }}After toLowerCase, s holds "programming". As i walks across it, the counter goes up at three places: the o, the a and the i. Every other letter is a consonant and is skipped, so the program prints Vowels: 3.
The line that does the work
The long if condition. c == 'a' || c == 'e' || ...reads as “is c an a, or an e, or one of the others?”. The || (or) joins the five tests: if any one of them is true, the whole condition is true and the character counts. Notice the single quotes. 'a' is a char, the same type charAt hands back, so each test compares a char with a char, and == is the right tool for that.
s = s.toLowerCase(); and change s to "AEIOU". What does the program print?Problem 3: is it a palindrome?#
A palindrome reads the same forwards and backwards: "racecar", "level", "noon". Given a string, decide true or false.
You could reverse the string and compare, and the puzzle at the end of this problem does exactly that. There is a tighter way, though, called two pointers. One position starts at the front and one at the back. Compare the characters there. If they ever differ, it is not a palindrome. If the front reaches the middle without finding a difference, every pair matched and it is a palindrome.
You do not even need two separate variables. The front character is s.charAt(i), and its mirror at the back is s.charAt(s.length() - 1 - i). As i grows, the mirror index shrinks. i only has to walk to the middle, so the loop runs while i < s.length() / 2.
public class Main { public static void main(String[] args) { String s = "racecar"; boolean isPalindrome = true; for (int i = 0; i < s.length() / 2; i++) { char front = s.charAt(i); char back = s.charAt(s.length() - 1 - i); if (front != back) { isPalindrome = false; } } System.out.println(isPalindrome); }}"racecar" has length 7, so the loop runs while i < 3: for i equal to 0, 1 and 2. Watch the pairs line up: r with the r at index 6, a with the a at index 5, c with the c at index 4. The middle e at index 3 has no partner and needs none. Nothing ever differed, so it prints true.
The line that does the work
s.charAt(s.length() - 1 - i). The last index of any string is s.length() - 1, because counting starts at 0. Subtracting i walks the mirror inward as i walks the front inward from the other side. When i is 0 the mirror is the very last character; when i is 1 it is the second to last. The two march toward each other and meet in the middle.
s.charAt(s.length() - i) and forget the - 1. On the first round that asks for s.charAt(s.length()), one past the end, and the program stops with a StringIndexOutOfBoundsException. When you reach from the back, the back index is length - 1, never length.To see the false case, change s to "hello" and run it again. On the very first round front is h and back is o. They differ, the flag turns false, and the program prints false. Now one that catches people out:
public class Main { public static void main(String[] args) { String s = "Racecar"; boolean isPalindrome = true; for (int i = 0; i < s.length() / 2; i++) { if (s.charAt(i) != s.charAt(s.length() - 1 - i)) { isPalindrome = false; } } System.out.println(isPalindrome); }}The same check, given "Racecar" with a capital R. What does it print?
It prints false. The first pair is 'R' and 'r', and to Java those are different characters with different codes, 82 and 114. A person reads Racecar as a palindrome; the program needs to be told to ignore case. The exercise below does that.
equals. Put the lines in order so the program prints level is a palindrome: true.Tap a line, then tap where it should go. Or use the arrows.
false for Racecar. Change it so capital and small letters count as the same. Racecar and level should print true, and Java should print false.Problem 4: count one character#
Given a string and a target character, count how many times the character appears. In "yellow umbrella", the letter l appears 4 times.
This is the vowel counter with a simpler question. Walk the string, and for each character ask the one question “is this my target?”. If it is, add one.
public class Main { public static void main(String[] args) { String s = "yellow umbrella"; char target = 'l'; int count = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == target) { count = count + 1; } } System.out.println("'" + target + "' appears " + count + " times"); }}The l shows up twice in yellow (at indexes 2 and 3), then twice more in umbrella (at indexes 12 and 13). Four in all, so the program prints 'l' appears 4 times.
The line that does the work
if (s.charAt(i) == target). Both sides are a char, so == compares them directly. The target has a variable of its own, so the same code counts any character: change target to 'e'and the loop counts the e's instead. Naming the thing you search for is a small habit that makes code easy to reuse.
a e i o u?||.== test.Key ideaCounting problems share one skeleton: a counter at zero, a walk across the string, and one question asked of each character. Only the question changes.
Problem 5: the first non-repeating character#
Find the first character in a string that appears exactly once. In "swiss" the s repeats, and w is the first letter that stands alone, so the answer is w.
For each character you need to know whether it appears anywhere else in the string. You can find out by counting how many times it shows up across the whole string. If that count is exactly 1, the character is unique, and because you check from the front, the first unique character you meet is the answer.
Counting one character takes a second loop inside the first: the outer loop picks a character, and the inner loop walks the whole string counting how often that character appears. A loop inside a loop is called a nested loop.
public class Main { public static void main(String[] args) { String s = "swiss"; char answer = '?'; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); int count = 0; for (int j = 0; j < s.length(); j++) { if (s.charAt(j) == c) { count = count + 1; } } if (count == 1) { answer = c; break; } } System.out.println("First non-repeating: " + answer); }}Step through it slowly. The outer loop first picks the sat index 0. The inner loop counts the s's across "swiss" and finds 3 (at indexes 0, 3 and 4), so s is not unique and the outer loop moves on. Next it picks the w at index 1, and the inner loop finds only 1. That is the answer: the program stores w and breaks out, then prints First non-repeating: w.
The line that does the work
The pair answer = c; then break;. The moment a count comes back as 1, you have found the first unique character, and there is no reason to keep searching. break leaves the outer loop at once. Without it, the loop would carry on and could overwrite answer with a later unique character. break is what makes it the first one rather than the last.
public class Main { public static void main(String[] args) { String s = "abcabc"; char answer = '?'; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); int count = 0; for (int j = 0; j < s.length(); j++) { if (s.charAt(j) == c) { count = count + 1; } } if (count == 1) { answer = c; break; } } System.out.println("First non-repeating: " + answer); }}Every letter of "abcabc" appears twice. What does the program print?
It prints First non-repeating: ?. No count ever comes back as 1, so answer = c never runs and answer keeps the '?'it started with. That starting value is the program's way of saying “none”, which is why it was chosen to be something that is not a letter.
If every character's count were worked out once and kept in a table, the second loop could look each count up instead of counting again. For the small letters a to z, an array of 26 numbers is that table: counts[0] for a, counts[1] for b, and so on. A letter's place in the array is its distance from 'a', and since a char is a number, c - 'a' works it out: 0 for a, 22 for w.
swiss it prints First non-repeating: w, and when no letter is unique it keeps the ?.a to z fit neatly into positions 0 to 25. For any character at all, capitals, digits and spaces included, the table you want is a map from each character to its count. Maps arrive in HashMap and HashSet, which counts letters this way.The patterns you just used#
Five problems, but only a few ideas underneath them. Knowing their names helps you solve the sixth problem, the one you have never seen before.
- Reverse built a new string: it started from
"", or an emptyStringBuilder, and added one character per round. - The vowel and character counts kept a counter at zero and added one whenever a test passed.
- The palindrome check used two pointers, walking in from both ends toward the middle.
- The first non-repeating character used a nested search, scanning the whole string again for each character, and then a table of counts to avoid the second scan.
Try it
Answer each one in your head or on paper first, then open the answer.
"noon" is a palindrome, and which pairs of characters does it compare?"java" becomes "Java", using only methods you know.for (int i = 0; i < s.length(); i++), still adding s.charAt(i) to reversed?FirstUnique.java, why does the inner loop start at j = 0 and not at j = i + 1?Practice
Work through these on your own. Each one builds on this page.
"banana" the result is "ban". Build a new result, and before adding a character, check whether the result already contains it."abc" becomes "def". Walk the string, turn each character into its number, add the shift, turn it back into a character, and add it to a result."hello" and then decoding the result gives "hello" again."listen" and "silent" are anagrams. Solve it twice: once with the counting pattern and a nested loop, and once with two arrays of 26 counts, one per word.Further reading#
Spotted a mistake, or a part that did not make sense? Report it by email