Loading problems...
TL;DR: The optimal solution uses the Knuth-Morris-Pratt (KMP) algorithm to efficiently find all occurrence indices of strings a and b in s, then filters the indices of a by checking if any index of b falls within the allowed distance k using binary search or two pointers.
This problem asks us to identify specific starting positions in a string s. A starting position i is considered "beautiful" if the substring starting at i matches string a, and there is another position j nearby (within distance k) where the substring matches string .
bThe challenge in LeetCode 3008 lies in the constraints. The strings can be up to 500,000 characters long. A naive comparison would result in a Time Limit Exceeded (TLE), necessitating an efficient string matching algorithm.
The brute force approach attempts to simulate the problem statement literally without optimization.
i in s.i, check if the substring s[i...i+len(a)-1] equals a.j in s.s[j...j+len(b)-1] equals b AND if |i - j| <= k.i to the result list.Pseudo-code:
pythonresult = [] for i from 0 to len(s) - len(a): if s[i : i+len(a)] == a: found_close_b = False for j from 0 to len(s) - len(b): if s[j : j+len(b)] == b and abs(i - j) <= k: found_close_b = True break if found_close_b: result.append(i) return result
Time Complexity Analysis: The string slicing and comparison take and respectively. The nested loops structure implies we might perform comparisons times. In the worst case, the complexity approaches . With , is , which is far beyond the limit of roughly operations allowed in one second. This approach fails due to Time Limit Exceeded.
The problem can be decomposed into two distinct phases:
a appears and all indices where string b appears.a, determine if a valid index of b exists within the range [i - k, i + k].The Search Phase is the bottleneck. Naive matching rescans characters in s repeatedly. The core insight of KMP is to utilize the internal structure of the pattern (specifically, repeating prefixes) to skip unnecessary comparisons. By preprocessing the pattern into a Longest Prefix Suffix (LPS) array, we can determine exactly how far to shift the pattern when a mismatch occurs, ensuring the pointer in s never moves backward.
The Filter Phase can be optimized by noting that the indices found will be sorted naturally (since we scan s from left to right). If we have a sorted list of indices for b, we can efficiently check for the existence of a value in the range [i - k, i + k] using binary search (specifically lower_bound) or a two-pointer approach.
Visual Description:
Imagine sliding pattern a over text s. When a mismatch occurs at index j of the pattern, instead of sliding the pattern by just one character and restarting the comparison from the beginning of the pattern, we consult the LPS array. The LPS value at j-1 tells us the length of the longest proper prefix of a[0...j-1] that is also a suffix of a[0...j-1]. We shift the pattern so that this prefix aligns with the matching suffix we just saw in the text. This allows the text pointer to continue moving forward continuously.

Let's trace the logic with s = "ababa", a = "aba", b = "b", k = 1.
Build LPS for a ("aba"):
LPS[0] = 0"ab": prefix "a", suffix "b" -> no match -> LPS[1] = 0"aba": prefix "a", suffix "a" -> match length 1 -> LPS[2] = 1[0, 0, 1]Search a in s:
s[0..2] == "aba"). indices_a adds 0.s[2..4] == "aba"). indices_a adds 2.indices_a = [0, 2]Search b in s:
b at indices 1 and 3.indices_b = [1, 3]Filter Indices:
i = 0 from indices_a:
[-1, 1].indices_b for first value >= -1. Found 1.1 <= 1? Yes. Add 0 to result.i = 2 from indices_a:
[1, 3].indices_b for first value >= 1. Found 1.Result: [0, 2].
The correctness relies on two pillars:
q to LPS[q-1] after a full match, we ensure overlapping occurrences are detected (e.g., finding "ana" twice in "banana").|i - j| <= k, which is equivalent to i - k <= j <= i + k. By finding the smallest j in indices_b such that j >= i - k (using binary search), we identify the only candidate that could possibly satisfy the lower bound condition while being minimal. If this candidate also satisfies j <= i + k, the condition is met. If the smallest valid candidate exceeds i + k, then no other candidate can satisfy the upper bound, as the list is sorted.cpp#include <vector> #include <string> #include <algorithm> #include <cmath> using namespace std; class Solution { public: // Helper function to compute the LPS array for KMP vector<int> computeLPS(const string& pattern) { int m = pattern.length(); vector<int> lps(m, 0); int len = 0; // Length of the previous longest prefix suffix int i = 1; while (i < m) { if (pattern[i] == pattern[len]) { len++; lps[i] = len; i++; } else { if (len != 0) { len = lps[len - 1]; } else { lps[i] = 0; i++; } } } return lps; } // KMP Search function to find all starting indices of pattern in text vector<int> kmpSearch(const string& text, const string& pattern) { vector<int> indices; if (pattern.empty()) return indices; vector<int> lps = computeLPS(pattern); int n = text.length(); int m = pattern.length(); int i = 0; // index for text int j = 0; // index for pattern while (i < n) { if (pattern[j] == text[i]) { j++; i++; } if (j == m) { indices.push_back(i - j); j = lps[j - 1]; // Reset j to find overlapping matches } else if (i < n && pattern[j] != text[i]) { if (j != 0) { j = lps[j - 1]; } else { i++; } } } return indices; } vector<int> beautifulIndices(string s, string a, string b, int k) { // Step 1: Find all occurrences of a and b using KMP vector<int> indices_a = kmpSearch(s, a); vector<int> indices_b = kmpSearch(s, b); vector<int> result; // Step 2: Filter indices of a based on distance to indices of b // Using binary search (lower_bound) since indices_b is sorted for (int i : indices_a) { // Find the first index in b that is >= i - k auto it = lower_bound(indices_b.begin(), indices_b.end(), i - k); // Check if such an index exists and is within the upper bound i + k if (it != indices_b.end() && *it <= i + k) { result.push_back(i); } } return result; } };
javaimport java.util.ArrayList; import java.util.Collections; import java.util.List; class Solution { // Helper to compute LPS array private int[] computeLPS(String pattern) { int m = pattern.length(); int[] lps = new int[m]; int len = 0; int i = 1; while (i < m) { 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; } // KMP Search implementation private List<Integer> kmpSearch(String text, String pattern) { List<Integer> indices = new ArrayList<>(); if (pattern.isEmpty()) return indices; int[] lps = computeLPS(pattern); int n = text.length(); int m = pattern.length(); int i = 0; int j = 0; while (i < n) { if (pattern.charAt(j) == text.charAt(i)) { j++; i++; } if (j == m) { indices.add(i - j); j = lps[j - 1]; } else if (i < n && pattern.charAt(j) != text.charAt(i)) { if (j != 0) { j = lps[j - 1]; } else { i++; } } } return indices; } public List<Integer> beautifulIndices(String s, String a, String b, int k) { List<Integer> indicesA = kmpSearch(s, a); List<Integer> indicesB = kmpSearch(s, b); List<Integer> result = new ArrayList<>(); // Filter indices for (int i : indicesA) { // Binary search for the first index in B >= i - k int searchVal = i - k; int idx = Collections.binarySearch(indicesB, searchVal); // binarySearch returns (-(insertion point) - 1) if not found if (idx < 0) { idx = -idx - 1; } // Check if valid index exists within range if (idx < indicesB.size() && indicesB.get(idx) <= i + k) { result.add(i); } } return result; } }
pythonfrom typing import List import bisect class Solution: def beautifulIndices(self, s: str, a: str, b: str, k: int) -> List[int]: def compute_lps(pattern): m = len(pattern) lps = [0] * m length = 0 i = 1 while i < m: if pattern[i] == pattern[length]: length += 1 lps[i] = length i += 1 else: if length != 0: length = lps[length - 1] else: lps[i] = 0 i += 1 return lps def kmp_search(text, pattern): indices = [] if not pattern: return indices lps = compute_lps(pattern) n = len(text) m = len(pattern) i = 0 # index for text j = 0 # index for pattern while i < n: if pattern[j] == text[i]: i += 1 j += 1 if j == m: indices.append(i - j) j = lps[j - 1] elif i < n and pattern[j] != text[i]: if j != 0: j = lps[j - 1] else: i += 1 return indices # Step 1: Get all occurrences indices_a = kmp_search(s, a) indices_b = kmp_search(s, b) result = [] # Step 2: Check distance constraint # indices_b is sorted, so we can use binary search for i in indices_a: # Find the first index in indices_b that is >= i - k idx = bisect.bisect_left(indices_b, i - k) # Check if this index exists and is <= i + k if idx < len(indices_b) and indices_b[idx] <= i + k: result.append(i) return result
Let be the length of s, be the length of a, and be the length of b.
Time Complexity:
a: .b: .The String Matching / KMP pattern is highly reusable. Understanding how to build and use the LPS array is crucial for several hard interview questions.
B exists in repeated A using KMP.s inside goal + goal using KMP.Why does my naive solution get Time Limit Exceeded (TLE)?
.find() or manual loops inside a loop without KMP logic.1 <= 3? Yes. Add 2 to result.s for a: .s for b: .a and b. The loop runs times. Inside, binary search takes . Total filtering is . Since , this is bounded by .Space Complexity:
indices_a and indices_b takes up to space in the worst case (e.g., s="aaaa", a="a").Why is my distance check failing?
indices_b for every i in indices_a.Why am I missing overlapping matches?
j to 0 after a match.j to lps[j-1] to preserve the prefix match.