- Home
- /
- Tutorials
- /
- DSA Tutorial
- /
- Common String Problems
Strings
Common String Problems
Four patterns cover most string questions. Once you can see which one you are looking at, the code is short.
Reversing
Two ways to reverse
javascript
// Readable. O(n) time, O(n) space.
function reverse(s) {
return [...s].reverse().join("")
}
// In place on an array of characters. O(1) extra space.
function reverseChars(chars) {
let left = 0
let right = chars.length - 1
while (left < right) {
const temp = chars[left]
chars[left] = chars[right]
chars[right] = temp
left++
right--
}
return chars
}
console.log(reverse("abc")) // "cba"
console.log(reverseChars(["a", "b", "c"])) // ["c", "b", "a"]Use the first unless the problem explicitly asks for constant space. Clear beats clever when the cost is the same.
Palindromes
The version interviews actually ask for ignores punctuation and case:
Palindrome, cleaned
javascript
function isPalindrome(s) {
const clean = s.toLowerCase().replace(/[^a-z0-9]/g, "")
let left = 0
let right = clean.length - 1
while (left < right) {
if (clean[left] !== clean[right]) return false
left++
right--
}
return true
}
console.log(isPalindrome("A man, a plan, a canal: Panama")) // trueO(n) time, O(n) space for the cleaned copy. You can do it in O(1) space by skipping non-letters with the pointers instead of cleaning first - worth knowing if you are asked.
First non-repeating character
Two passes beat nested loops
javascript
function firstUnique(s) {
const counts = new Map()
for (const ch of s) counts.set(ch, (counts.get(ch) || 0) + 1)
for (let i = 0; i < s.length; i++) {
if (counts.get(s[i]) === 1) return i
}
return -1
}
console.log(firstUnique("leetcode")) // 0
console.log(firstUnique("aabb")) // -1Two separate passes, still O(n). Beginners often reach for a nested loop here and land on O(n squared) for no gain.
Longest run of unique characters
This is a sliding window, covered properly later - but it is the string problem that appears most often, so it is worth seeing now:
Sliding window
javascript
function longestUnique(s) {
const lastSeen = new Map()
let best = 0
let start = 0
for (let i = 0; i < s.length; i++) {
const ch = s[i]
if (lastSeen.has(ch) && lastSeen.get(ch) >= start) {
start = lastSeen.get(ch) + 1
}
lastSeen.set(ch, i)
best = Math.max(best, i - start + 1)
}
return best
}
console.log(longestUnique("abcabcbb")) // 3One pass. The window never moves backwards, which is what keeps it O(n) rather than O(n squared).
Counting with a fixed alphabet
When the input is promised to be lowercase English letters, a 26-element array beats a Map. Index with charCodeAt(i) - 97. It is the same O(n) but with no hashing and no object allocation, and it makes the space bound obviously O(1).
Array counting for a known alphabet
javascript
function isAnagram(a, b) {
if (a.length !== b.length) return false
const counts = new Array(26).fill(0)
for (let i = 0; i < a.length; i++) {
counts[a.charCodeAt(i) - 97]++
counts[b.charCodeAt(i) - 97]--
}
return counts.every((n) => n === 0)
}
console.log(isAnagram("listen", "silent")) // true
console.log(isAnagram("hello", "world")) // falseOne pass over both strings at once, incrementing for the first and decrementing for the second. If they match, every counter returns to zero.
Grouping anagrams
The follow-up is almost always "group a list of words into anagram sets". The move is to build a canonical key for each word - either its sorted letters or its count signature - and use that key in a Map. Each word is visited once, so it stays linear in the total number of characters.
- Palindrome - two pointers from both ends.
- Anagram - count, do not sort.
- First unique - count, then scan in order.
- Longest unique run - sliding window with last-seen indices.
- Group anagrams - canonical key into a Map.
