Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
You are given a 0-indexed array of unique strings words.
A palindrome pair is a pair of integers (i, j) such that:
0 <= i, j < words.length,i != j, andwords[i] + words[j] (the concatenation of the two strings) is a palindrome.Return an array of all the palindrome pairs of words.
You must write an algorithm with O(sum of words[i].length) runtime complexity.
Example 1:
Input: words = ["abcd","dcba","lls","s","sssll"] Output: [[0,1],[1,0],[3,2],[2,4]] Explanation: The palindromes are ["abcddcba","dcbaabcd","slls","llssssll"]
Example 2:
Input: words = ["bat","tab","cat"] Output: [[0,1],[1,0]] Explanation: The palindromes are ["battab","tabbat"]
Example 3:
Input: words = ["a",""] Output: [[0,1],[1,0]] Explanation: The palindromes are ["a","a"]
Constraints:
1 <= words.length <= 50000 <= words[i].length <= 300words[i] consists of lowercase English letters.Problem summary: You are given a 0-indexed array of unique strings words. A palindrome pair is a pair of integers (i, j) such that: 0 <= i, j < words.length, i != j, and words[i] + words[j] (the concatenation of the two strings) is a palindrome. Return an array of all the palindrome pairs of words. You must write an algorithm with O(sum of words[i].length) runtime complexity.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Hash Map · Trie
["abcd","dcba","lls","s","sssll"]
["bat","tab","cat"]
["a",""]
longest-palindromic-substring)shortest-palindrome)longest-palindrome-by-concatenating-two-letter-words)find-maximum-number-of-string-pairs)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #336: Palindrome Pairs
class Solution {
private static final int BASE = 131;
private static final long[] MUL = new long[310];
private static final int MOD = (int) 1e9 + 7;
static {
MUL[0] = 1;
for (int i = 1; i < MUL.length; ++i) {
MUL[i] = (MUL[i - 1] * BASE) % MOD;
}
}
public List<List<Integer>> palindromePairs(String[] words) {
int n = words.length;
long[] prefix = new long[n];
long[] suffix = new long[n];
for (int i = 0; i < n; ++i) {
String word = words[i];
int m = word.length();
for (int j = 0; j < m; ++j) {
int t = word.charAt(j) - 'a' + 1;
int s = word.charAt(m - j - 1) - 'a' + 1;
prefix[i] = (prefix[i] * BASE) % MOD + t;
suffix[i] = (suffix[i] * BASE) % MOD + s;
}
}
List<List<Integer>> ans = new ArrayList<>();
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (check(i, j, words[j].length(), words[i].length(), prefix, suffix)) {
ans.add(Arrays.asList(i, j));
}
if (check(j, i, words[i].length(), words[j].length(), prefix, suffix)) {
ans.add(Arrays.asList(j, i));
}
}
}
return ans;
}
private boolean check(int i, int j, int n, int m, long[] prefix, long[] suffix) {
long t = ((prefix[i] * MUL[n]) % MOD + prefix[j]) % MOD;
long s = ((suffix[j] * MUL[m]) % MOD + suffix[i]) % MOD;
return t == s;
}
}
// Accepted solution for LeetCode #336: Palindrome Pairs
func palindromePairs(words []string) [][]int {
base := 131
mod := int(1e9) + 7
mul := make([]int, 310)
mul[0] = 1
for i := 1; i < len(mul); i++ {
mul[i] = (mul[i-1] * base) % mod
}
n := len(words)
prefix := make([]int, n)
suffix := make([]int, n)
for i, word := range words {
m := len(word)
for j, c := range word {
t := int(c-'a') + 1
s := int(word[m-j-1]-'a') + 1
prefix[i] = (prefix[i]*base)%mod + t
suffix[i] = (suffix[i]*base)%mod + s
}
}
check := func(i, j, n, m int) bool {
t := ((prefix[i]*mul[n])%mod + prefix[j]) % mod
s := ((suffix[j]*mul[m])%mod + suffix[i]) % mod
return t == s
}
var ans [][]int
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
if check(i, j, len(words[j]), len(words[i])) {
ans = append(ans, []int{i, j})
}
if check(j, i, len(words[i]), len(words[j])) {
ans = append(ans, []int{j, i})
}
}
}
return ans
}
# Accepted solution for LeetCode #336: Palindrome Pairs
class Solution:
def palindromePairs(self, words: List[str]) -> List[List[int]]:
d = {w: i for i, w in enumerate(words)}
ans = []
for i, w in enumerate(words):
for j in range(len(w) + 1):
a, b = w[:j], w[j:]
ra, rb = a[::-1], b[::-1]
if ra in d and d[ra] != i and b == rb:
ans.append([i, d[ra]])
if j and rb in d and d[rb] != i and a == ra:
ans.append([d[rb], i])
return ans
// Accepted solution for LeetCode #336: Palindrome Pairs
struct Solution;
use std::collections::HashMap;
use std::collections::HashSet;
impl Solution {
fn palindrome_pairs(words: Vec<String>) -> Vec<Vec<i32>> {
let mut id: HashMap<String, usize> = HashMap::new();
let n = words.len();
let mut res = HashSet::new();
for i in 0..n {
id.insert(words[i].to_string(), i);
}
for i in 0..n {
let k = words[i].len();
for mid in 0..=k {
let left: String = words[i][0..mid].to_string();
let right: String = words[i][mid..].to_string();
if Self::is_palindrome(&left) {
let right_r: String = right.chars().rev().collect();
if let Some(&j) = id.get(&right_r) {
if i != j {
res.insert(vec![j as i32, i as i32]);
}
}
}
if Self::is_palindrome(&right) {
let left_r: String = left.chars().rev().collect();
if let Some(&j) = id.get(&left_r) {
if i != j {
res.insert(vec![i as i32, j as i32]);
}
}
}
}
}
res.into_iter().collect()
}
fn is_palindrome(s: &str) -> bool {
!s.chars().zip(s.chars().rev()).any(|(a, b)| a != b)
}
}
#[test]
fn test() {
let words = vec_string!["abcd", "dcba", "lls", "s", "sssll"];
let mut res = vec![[0, 1], [1, 0], [3, 2], [2, 4]];
let mut ans = Solution::palindrome_pairs(words);
res.sort_unstable();
ans.sort_unstable();
assert_eq!(ans, res);
let words = vec_string!["bat", "tab", "cat"];
let mut res = vec![[0, 1], [1, 0]];
let mut ans = Solution::palindrome_pairs(words);
res.sort_unstable();
ans.sort_unstable();
assert_eq!(ans, res);
}
// Accepted solution for LeetCode #336: Palindrome Pairs
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #336: Palindrome Pairs
// class Solution {
// private static final int BASE = 131;
// private static final long[] MUL = new long[310];
// private static final int MOD = (int) 1e9 + 7;
// static {
// MUL[0] = 1;
// for (int i = 1; i < MUL.length; ++i) {
// MUL[i] = (MUL[i - 1] * BASE) % MOD;
// }
// }
// public List<List<Integer>> palindromePairs(String[] words) {
// int n = words.length;
// long[] prefix = new long[n];
// long[] suffix = new long[n];
// for (int i = 0; i < n; ++i) {
// String word = words[i];
// int m = word.length();
// for (int j = 0; j < m; ++j) {
// int t = word.charAt(j) - 'a' + 1;
// int s = word.charAt(m - j - 1) - 'a' + 1;
// prefix[i] = (prefix[i] * BASE) % MOD + t;
// suffix[i] = (suffix[i] * BASE) % MOD + s;
// }
// }
// List<List<Integer>> ans = new ArrayList<>();
// for (int i = 0; i < n; ++i) {
// for (int j = i + 1; j < n; ++j) {
// if (check(i, j, words[j].length(), words[i].length(), prefix, suffix)) {
// ans.add(Arrays.asList(i, j));
// }
// if (check(j, i, words[i].length(), words[j].length(), prefix, suffix)) {
// ans.add(Arrays.asList(j, i));
// }
// }
// }
// return ans;
// }
//
// private boolean check(int i, int j, int n, int m, long[] prefix, long[] suffix) {
// long t = ((prefix[i] * MUL[n]) % MOD + prefix[j]) % MOD;
// long s = ((suffix[j] * MUL[m]) % MOD + suffix[i]) % MOD;
// return t == s;
// }
// }
Use this to step through a reusable interview workflow for this problem.
Store all N words in a hash set. Each insert/lookup hashes the entire word of length L, giving O(L) per operation. Prefix queries require checking every stored word against the prefix — O(N × L) per prefix search. Space is O(N × L) for storing all characters.
Each operation (insert, search, prefix) takes O(L) time where L is the word length — one node visited per character. Total space is bounded by the sum of all stored word lengths. Tries win over hash sets when you need prefix matching: O(L) prefix search vs. checking every stored word.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Wrong move: Zero-count keys stay in map and break distinct/count constraints.
Usually fails on: Window/map size checks are consistently off by one.
Fix: Delete keys when count reaches zero.