String Fundamentals
What is a String?
A String in Java is an immutable sequence of characters. Once created, it cannot be changed.
String s = "Hello";
// s is stored in the String Pool (special memory area)
// The characters 'H', 'e', 'l', 'l', 'o' cannot be modified
String Memory Layout
String s = "Hello";
String Pool:
+---+---+---+---+---+
| H | e | l | l | o |
+---+---+---+---+---+
↑
s (reference)
Why Immutable?
- Security: Strings are used for class loading, network connections
- Thread Safety: Immutable objects are inherently thread-safe
- String Pooling: Can reuse identical strings
- Hashing: Hash code can be cached
String Declaration
// Literal (stored in String Pool)
String s1 = "Hello";
// New object (stored in heap)
String s2 = new String("Hello");
// They are equal in content but different objects
s1.equals(s2); // true (content comparison)
s1 == s2; // false (reference comparison)
Common Operations
String s = "Hello, World!";
// Length
int len = s.length(); // 13
// Access character
char c = s.charAt(0); // 'H'
// Substring
String sub = s.substring(0, 5); // "Hello"
String sub2 = s.substring(7); // "World!"
// Find index
int idx = s.indexOf('W'); // 7
int idx2 = s.indexOf("World"); // 7
// Case conversion
String upper = s.toUpperCase(); // "HELLO, WORLD!"
String lower = s.toLowerCase(); // "hello, world!"
// Trim whitespace
String padded = " hello ";
padded.trim(); // "hello"
// Replace
String replaced = s.replace('o', '0'); // "Hell0, W0rld!"
// Split
String[] words = s.split(", "); // ["Hello", "World!"]
// Join
String joined = String.join("-", "a", "b", "c"); // "a-b-c"
// Starts/Ends with
boolean starts = s.startsWith("Hello"); // true
boolean ends = s.endsWith("!"); // true
Immutability Gotcha
// Each operation creates a NEW String
String s = "Hello";
s = s + " World"; // Creates new String "Hello World"
// Original "Hello" still exists in memory
// This is O(n) for concatenation!
for (int i = 0; i < 10000; i++) {
s = s + "a"; // Very slow: O(n²) total
}
StringBuilder
The Problem with String Concatenation
// BAD: O(n²) time
String result = "";
for (int i = 0; i < n; i++) {
result += "a"; // Creates new String each time
}
The Solution: StringBuilder
// GOOD: O(n) time
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append("a"); // Modifies internal buffer
}
String result = sb.toString();
StringBuilder Operations
StringBuilder sb = new StringBuilder();
// Append
sb.append("Hello");
sb.append(' ');
sb.append(123); // Auto-converts to string
// Insert
sb.insert(5, ","); // "Hello, 123"
// Delete
sb.delete(5, 6); // "Hello 123"
sb.deleteCharAt(0); // "ello 123"
// Replace
sb.replace(0, 1, "H"); // "Hello 123"
// Reverse
sb.reverse(); // "321 olleH"
// Length
int len = sb.length(); // 9
// Convert to String
String result = sb.toString();
When to Use StringBuilder
| Situation | Use |
|---|---|
| Single concatenation | String |
| Loop concatenation | StringBuilder |
| Building string character by character | StringBuilder |
| String is already final | String |
Performance Comparison
// String: O(n²)
String s = "";
for (int i = 0; i < 100000; i++) {
s += "a"; // Very slow
}
// StringBuilder: O(n)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("a"); // Very fast
}
For n=100,000:
- String: ~10 seconds
- StringBuilder: ~0.01 seconds
Palindrome Problems
What is a Palindrome?
A string that reads the same forwards and backwards.
Examples:
- "racecar" → palindrome
- "hello" → not palindrome
- "A man a plan a canal Panama" → palindrome (ignoring spaces and case)
Basic Palindrome Check
boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
// Time: O(n), Space: O(1)
Valid Palindrome (LeetCode 125)
Consider only alphanumeric characters and ignore cases.
boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
left++;
}
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
right--;
}
if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}
Longest Palindromic Substring (LeetCode 5)
// Expand around center approach
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) return "";
int start = 0, maxLen = 0;
for (int i = 0; i < s.length(); i++) {
// Check odd-length palindromes
int len1 = expandAroundCenter(s, i, i);
// Check even-length palindromes
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > maxLen) {
start = i - (len - 1) / 2;
maxLen = len;
}
}
return s.substring(start, start + maxLen);
}
private int expandAroundCenter(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}
// Time: O(n²), Space: O(1)
Palindrome Patterns
- Two Pointers: Basic palindrome check
- Expand Around Center: Find palindromic substrings
- Dynamic Programming: Count palindromic substrings
- Manacher's Algorithm: Find all palindromes in O(n)
Anagram Problems
What is an Anagram?
Two strings are anagrams if they contain the same characters with the same frequencies.
Examples:
- "listen" and "silent" → anagrams
- "hello" and "bello" → not anagrams
Check if Two Strings are Anagrams
Approach 1: Sorting
boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
char[] sArr = s.toCharArray();
char[] tArr = t.toCharArray();
Arrays.sort(sArr);
Arrays.sort(tArr);
return Arrays.equals(sArr, tArr);
}
// Time: O(n log n), Space: O(n)
Approach 2: Character Count
boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
int[] count = new int[26];
for (int i = 0; i < s.length(); i++) {
count[s.charAt(i) - 'a']++;
count[t.charAt(i) - 'a']--;
}
for (int c : count) {
if (c != 0) return false;
}
return true;
}
// Time: O(n), Space: O(1)
Group Anagrams (LeetCode 49)
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] chars = str.toCharArray();
Arrays.sort(chars);
String sorted = new String(chars);
map.computeIfAbsent(sorted, k -> new ArrayList<>()).add(str);
}
return new ArrayList<>(map.values());
}
// Time: O(n × k log k), Space: O(n × k)
// where n = number of strings, k = max string length
Find All Anagrams in a String (LeetCode 438)
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
if (s.length() < p.length()) return result;
int[] pCount = new int[26];
int[] sCount = new int[26];
for (int i = 0; i < p.length(); i++) {
pCount[p.charAt(i) - 'a']++;
sCount[s.charAt(i) - 'a']++;
}
if (Arrays.equals(pCount, sCount)) {
result.add(0);
}
for (int i = p.length(); i < s.length(); i++) {
sCount[s.charAt(i) - 'a']++;
sCount[s.charAt(i - p.length()) - 'a']--;
if (Arrays.equals(pCount, sCount)) {
result.add(i - p.length() + 1);
}
}
return result;
}
// Time: O(n), Space: O(1)
Anagram Patterns
- Sorting: Simple but O(n log n)
- Character Count: Optimal O(n)
- Sliding Window: For finding anagram substrings
String Pattern Matching
Naive Approach
// Search for pattern in text
int strStr(String haystack, String needle) {
if (needle.isEmpty()) return 0;
for (int i = 0; i <= haystack.length() - needle.length(); i++) {
int j = 0;
while (j < needle.length() && haystack.charAt(i + j) == needle.charAt(j)) {
j++;
}
if (j == needle.length()) return i;
}
return -1;
}
// Time: O(n × m), Space: O(1)
KMP Algorithm (Knuth-Morris-Pratt)
KMP avoids redundant comparisons by using information from previous matches.
public int strStr(String haystack, String needle) {
if (needle.isEmpty()) return 0;
int[] lps = buildLPS(needle);
int i = 0, j = 0;
while (i < haystack.length()) {
if (haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
if (j == needle.length()) {
return i - j;
}
} else {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return -1;
}
private int[] buildLPS(String pattern) {
int[] lps = new int[pattern.length()];
int len = 0;
int i = 1;
while (i < pattern.length()) {
if (pattern.charAt(i) == pattern.charAt(len)) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
// Time: O(n + m), Space: O(m)
Rabin-Karp (Rolling Hash)
public int strStr(String haystack, String needle) {
if (needle.isEmpty()) return 0;
int n = haystack.length();
int m = needle.length();
int base = 256;
int mod = 101;
int needleHash = 0;
int windowHash = 0;
int basePowM = 1;
// Precompute base^(m-1) % mod
for (int i = 0; i < m - 1; i++) {
basePowM = (basePowM * base) % mod;
}
// Compute initial hashes
for (int i = 0; i < m; i++) {
needleHash = (needleHash * base + needle.charAt(i)) % mod;
windowHash = (windowHash * base + haystack.charAt(i)) % mod;
}
// Slide the window
for (int i = 0; i <= n - m; i++) {
if (needleHash == windowHash) {
// Verify match
if (haystack.substring(i, i + m).equals(needle)) {
return i;
}
}
if (i < n - m) {
windowHash = (windowHash - haystack.charAt(i) * basePowM) * base + haystack.charAt(i + m);
windowHash = ((windowHash % mod) + mod) % mod;
}
}
return -1;
}
// Time: O(n + m) average, Space: O(1)
When to Use Each
| Algorithm | Time | Space | Best For |
|---|---|---|---|
| Naive | O(nm) | O(1) | Small texts |
| KMP | O(n+m) | O(m) | Single pattern |
| Rabin-Karp | O(n+m) | O(1) | Multiple patterns |
Practice Problems
A phrase is a palindrome if, after converting all uppercase letters into lowercase letters and removing all non-alphanumeric characters, it reads the same forward and backward.
Example:
Input: s = "A man, a plan, a canal: Panama"
Output: true
"amanaplanacanalpanama" is a palindrome.
Optimal Solution — O(n) time, O(1) space
Two pointers from both ends
class Solution {
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) left++;
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) right--;
if (Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}
}Edge Cases:
- Empty string
- Single character
- All non-alphanumeric
- Mixed case
Given an array of strings strs, group the anagrams together. You can return the answer in any order.
Example:
Input: strs = ["eat","tea","tan","ate","nat","bat"]
Output: [["bat"],["nat","tan"],["ate","eat","tea"]]
Group strings with same characters.
Optimal Solution — O(n × k log k) time, O(n × k) space
HashMap with sorted string as key
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] chars = str.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
map.computeIfAbsent(key, k -> new ArrayList<>()).add(str);
}
return new ArrayList<>(map.values());
}
}Edge Cases:
- Empty array
- Single string
- All same strings
- Different lengths
Given two strings s and t of lengths m and n respectively, return the minimum window substring of s such that every character in t (including duplicates) is included in the window.
Example:
Input: s = "ADOBECODEBANC", t = "ABC"
Output: "BANC"
The minimum window substring 'BANC' includes 'A', 'B', and 'C' from string t.
Optimal Solution — O(m + n) time, O(m + n) space
Sliding window with character frequency map
class Solution {
public String minWindow(String s, String t) {
if (s.length() < t.length()) return "";
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> have = new HashMap<>();
for (char c : t.toCharArray()) {
need.put(c, need.getOrDefault(c, 0) + 1);
}
int haveCount = 0, needCount = need.size();
int left = 0, minLen = Integer.MAX_VALUE, minStart = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
have.put(c, have.getOrDefault(c, 0) + 1);
if (have.getOrDefault(c, 0).equals(need.getOrDefault(c, 0))) {
haveCount++;
}
while (haveCount == needCount) {
if (right - left + 1 < minLen) {
minLen = right - left + 1;
minStart = left;
}
char leftChar = s.charAt(left);
have.put(leftChar, have.get(leftChar) - 1);
if (have.get(leftChar) < need.getOrDefault(leftChar, 0)) {
haveCount--;
}
left++;
}
}
return minLen == Integer.MAX_VALUE ? "" : s.substring(minStart, minStart + minLen);
}
}Edge Cases:
- s is shorter than t: return empty
- t has all same characters
- No valid window exists
- s equals t
Quiz
1. Why are Strings immutable in Java?
2. What is the time complexity of String concatenation with + operator?
3. Which algorithm is best for finding a pattern in a string?
4. What is the primary purpose of Strings?
Flashcards
Question
Why use StringBuilder instead of String for concatenation?
Click to reveal answer
Answer
StringBuilder modifies an internal buffer in O(1) amortized time. String creates new objects in O(n) time each.
Question
How do you check if two strings are anagrams?
Click to reveal answer
Answer
Sort both and compare (O(n log n)), or count characters using an array (O(n)).
Question
What is the LPS array in KMP?
Click to reveal answer
Answer
Longest Proper Prefix which is also Suffix. It helps avoid redundant comparisons.
Question
What is Strings?
Click to reveal answer
Answer
Strings is a key concept in software engineering.
Question
When to use Strings?
Click to reveal answer
Answer
Use Strings when building production systems that require reliability, scalability, and maintainability.
Revision Notes
Key Takeaways
- 1.Strings are immutable - use StringBuilder for modifications
- 2.Character counting is O(n) and uses O(1) space
- 3.Two pointers is the go-to for palindrome problems
- 4.HashMap enables grouping and frequency problems
- 5.KMP achieves O(n+m) for pattern matching
Interview Tips
- •Clarify if case matters (uppercase/lowercase)
- •Ask about Unicode or ASCII only
- •Discuss time-space tradeoffs
- •Handle empty strings and single characters
Cheat Sheet
Strings Cheat Sheet
Key Facts:
- Strings are immutable in Java
- Use StringBuilder for loops
- String concatenation is O(n)
Common Patterns:
- Two Pointers - palindrome check
- Character Count - anagram check
- Sliding Window - substring problems
- HashMap - frequency problems
- Sorting - grouping problems
Algorithms:
| Algorithm | Use Case |
|---|---|
| KMP | Single pattern matching |
| Rabin-Karp | Multiple pattern matching |
| Manacher | Palindrome finding |