String Manipulation
Treat a string as an array of characters and solve parsing, comparison and rebuilding tasks with index walks, two pointers and a StringBuilder in linear time.
Instinct Reading, comparing or rebuilding text character by character? Walk indices on a char[] and build output with a StringBuilder, never with + in a loop.
Memory hack Chars in an array, pointers on the ends, builder at the back - strings are just arrays wearing a costume.
At a glance
When to use it
- You must check, compare or rebuild text character by character
- Characters such as spaces, punctuation or case should be skipped or normalised
- The output is a reordered, reformatted or encoded version of the input
- Two strings are walked together, as in subsequence or prefix checks
Key idea
Treat the string as a char array, move one or two indices with clear stop rules and collect output in a single StringBuilder, so the job stays O(n) instead of the O(n^2) that repeated concatenation causes.
Typical complexity
O(n) time, O(1) space for checks or O(n) for the built output
| # | Common problems | Level | Our solution | Practice |
|---|---|---|---|---|
| 1 | Valid Palindrome | Easy | Read solution | LeetCode ↗ |
| 2 | Is Subsequence | Easy | Read solution | LeetCode ↗ |
| 3 | Longest Common Prefix | Easy | Read solution | LeetCode ↗ |
| 4 | Reverse Words in a String | Medium | Read solution | LeetCode ↗ |
| 5 | Zigzag Conversion | Medium | Read solution | LeetCode ↗ |
| 6 | Encode and Decode Strings | Medium | Read solution | LeetCode ↗ |
The instinct
A string is an array of char that happens to be immutable in Java. Most "string manipulation" questions are really careful index bookkeeping: skip what does not matter (spaces, punctuation, case), compare what does, and assemble a new string efficiently. The pattern is less about a clever idea and more about clean, bug-free traversal: knowing which index moves, when it stops, and how you build the result without accidentally going quadratic.
When you read a problem about characters, words, prefixes, palindromes or encoding, your default plan should be: convert to char[] if you need to mutate, walk with one or two indices, and collect output in a StringBuilder.
Recognise it
| Signal in the problem | Why it points here |
|---|---|
| "Ignoring non-alphanumeric characters and case" | Filter while walking two indices; no need to build a cleaned copy. |
| "Reverse the words", "extra spaces between words" | Tokenise by scanning, then rebuild with a builder in the new order. |
| "Is s a subsequence of t" | One pointer per string; advance the pattern pointer only on a match. |
| "Longest common prefix among all strings" | Compare column by column (vertical scan) until any string disagrees. |
| "Write the string in a zigzag / row-by-row pattern" | Pure simulation: compute which row or index each character lands on. |
| "Encode a list of strings into one string and decode it back" | Design a delimiter-safe format such as length prefixes. |
| "Rotate / shift characters", "compress runs" | Index arithmetic with modulo, or a run-length counter while scanning. |
Not this pattern if...
- You need the longest/shortest substring satisfying a constraint: use Sliding Window.
- The question is about character frequencies, anagrams or "can be rearranged": use Hashing & Counting.
- You must count or find palindromic subsequences or edit distance: that is Dynamic Programming.
- You need fast repeated pattern matching (KMP, rolling hash): that is a dedicated string-matching algorithm.
Memory hack
Chars in an array, pointers on the ends, builder at the back. Strings are just arrays wearing a costume.
Think of a typesetter at an old printing press: they never carve a new block for every letter they add. They pick letters from the tray one at a time (index walk) and slot them into a composing stick (the StringBuilder), then print once at the end (toString()).
Say it out loud in the interview:
- "What is the character set and is case significant?" (ASCII lowercase means an
int[26]or simple comparisons.) - "Which indices am I walking, and what makes each one move or stop?"
- "I'll build output with a StringBuilder and convert once, so the whole thing stays O(n)."
How it works
Most problems use one of three skeletons:
- Converging pointers (Valid Palindrome):
ifrom the left,jfrom the right, each skipping characters that do not count, then comparing lowercase forms. - Chasing pointers (Is Subsequence): one pointer walks the long string every step; the other walks the short string only on a match. If the short pointer reaches the end, it is a subsequence.
- Scan and rebuild (Reverse Words in a String, Zigzag Conversion, Encode and Decode Strings): read tokens or positions, then write them into a builder in a new order.
flowchart LR
A[Input string] --> B{Need to mutate?}
B -- yes --> C[toCharArray]
B -- no --> D[charAt walk]
C --> E[Move indices with clear stop rules]
D --> E
E --> F{Producing new text?}
F -- yes --> G[Append to StringBuilder]
F -- no --> H[Return boolean / index / count]
G --> I[toString once]
For Encode and Decode Strings, a robust format is length#payload for each item: ["ab", "c#d"] becomes 2#ab3#c#d. While decoding, read digits up to #, then take exactly that many characters, so delimiters inside the payload never confuse you.
Java template
import java.util.ArrayList; import java.util.List; class StringToolkit { // Converging pointers with a skip rule (palindrome-style checks). static boolean symmetricCheck(String s) { int i = 0, j = s.length() - 1; while (i < j) { while (i < j && !counts(s.charAt(i))) i++; // skip noise on the left while (i < j && !counts(s.charAt(j))) j--; // skip noise on the right if (Character.toLowerCase(s.charAt(i)) != Character.toLowerCase(s.charAt(j))) return false; i++; j--; } return true; } static boolean counts(char c) { return Character.isLetterOrDigit(c); // problem-specific filter } // Scan tokens, then rebuild in a new order with one StringBuilder. static String rebuildWords(String s) { List<String> words = new ArrayList<>(); int i = 0, n = s.length(); while (i < n) { while (i < n && s.charAt(i) == ' ') i++; // skip separators int start = i; while (i < n && s.charAt(i) != ' ') i++; // consume one token if (start < i) words.add(s.substring(start, i)); } StringBuilder sb = new StringBuilder(); for (int k = words.size() - 1; k >= 0; k--) { // problem-specific order if (sb.length() > 0) sb.append(' '); sb.append(words.get(k)); } return sb.toString(); } // Length-prefixed encoding: safe for any characters inside the payload. static String encode(List<String> items) { StringBuilder sb = new StringBuilder(); for (String it : items) sb.append(it.length()).append('#').append(it); return sb.toString(); } static List<String> decode(String s) { List<String> out = new ArrayList<>(); int i = 0; while (i < s.length()) { int hash = s.indexOf('#', i); int len = Integer.parseInt(s.substring(i, hash)); out.add(s.substring(hash + 1, hash + 1 + len)); i = hash + 1 + len; } return out; } }
Complexity
- Index walks and two-pointer checks: O(n) time, O(1) extra space when you use
charAt. - Rebuilding with a
StringBuilder: O(n) time and O(n) space for the output. - Longest Common Prefix (vertical scan): O(S) where S is the total number of characters examined.
toCharArray()costs O(n) space; that is fine unless the interviewer insists on constant memory.- Repeated
s = s + cinside a loop is O(n^2) because each concatenation copies the whole string.
Variations
- Palindrome checks: converging pointers with a filter. Valid Palindrome; expand-from-centre is the cousin for finding palindromic substrings.
- Subsequence matching: chasing pointers across two strings. Is Subsequence.
- Prefix comparison: vertical scan, or sort and compare only the first and last strings. Longest Common Prefix.
- Tokenise and reorder: split on separators manually and rebuild. Reverse Words in a String.
- Layout simulation: map each character to a row with a bouncing direction. Zigzag Conversion.
- Serialisation: length prefixes or escaping. Encode and Decode Strings.
- Interactive elimination: compare candidate words by matching positions and filter the pool after each guess. Guess the Word.
Common mistakes
- Using
==to compareStringobjects instead of.equals();==checks identity, not content. - Off-by-one errors with
substring(start, end), whose end index is exclusive. - Forgetting the
i < jguard inside the skip loops, which lets pointers cross or run out of bounds on strings like".,". - Splitting with
s.split(" ")and getting empty tokens when there are multiple spaces; usetrim()plussplit("\\s+"), or scan manually. - Choosing a delimiter for encoding (like a comma) that can also appear inside the data.
result += ch inside a loop silently turns an O(n) solution into O(n^2), because Java strings are immutable and every + copies the entire string. Always build with StringBuilder and call toString() once.
All practice problems
7 problems on this site use this pattern, easiest first. Solve them in order and say the instinct out loud before you code.