String Manipulation

Easystrings · two pointers · simulation · parsing⏱ 6 min readUpdated

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.

PatternString Manipulation

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

How to spot it If the problem says "ignoring case and non-alphanumerics", "reverse the words" or "encode and decode" and asks for a check or a rebuilt string, think string manipulation first.

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

Tip

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:

  1. "What is the character set and is case significant?" (ASCII lowercase means an int[26] or simple comparisons.)
  2. "Which indices am I walking, and what makes each one move or stop?"
  3. "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:

  1. Converging pointers (Valid Palindrome): i from the left, j from the right, each skipping characters that do not count, then comparing lowercase forms.
  2. 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.
  3. 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

java
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 + c inside 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 compare String objects instead of .equals(); == checks identity, not content.
  • Off-by-one errors with substring(start, end), whose end index is exclusive.
  • Forgetting the i < j guard 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; use trim() plus split("\\s+"), or scan manually.
  • Choosing a delimiter for encoding (like a comma) that can also appear inside the data.
Warning

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.

My notes saved in this browser