Unique Substrings in Wraparound String
MedPrompt
We define the string base to be the infinite wraparound string of "abcdefghijklmnopqrstuvwxyz", so base will look like this:
"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....".
Given a string s, return the number of unique non-empty substrings of s are present in base.
Example 1:
Input: s = "a"
Output: 1
Explanation: Only the substring "a" of s is in base.Example 2:
Input: s = "cac"
Output: 2
Explanation: There are two substrings ("a", "c") of s in base.Example 3:
Input: s = "zab"
Output: 6
Explanation: There are six substrings ("z", "a", "b", "za", "ab", and "zab") of s in base.
Constraints:
1 <= s.length <= 105sconsists of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves generating all possible substrings of the input string s. For each substring, we check if it is a valid 'wraparound' substring according to the problem definition. All unique valid substrings are stored in a HashSet to avoid duplicates. The final answer is the size of this set.
Algorithm
- Initialize an empty
HashSet<String>calleduniqueSubstrings. - Iterate through the string
swith an indexifrom0tos.length() - 1to define the start of a substring. - Start a nested loop with index
jfromitos.length() - 1. - In the inner loop, check if the character
s.charAt(j)followss.charAt(j-1)(forj > i). - If they are consecutive, the substring
s.substring(i, j+1)is valid. Add it touniqueSubstrings. - If they are not consecutive, break the inner loop, as any longer substring starting at
iwill also be invalid. - After the loops complete, return the size of
uniqueSubstrings.
Walkthrough
The core idea is to systematically explore every substring of s. We can use nested loops where the outer loop selects a starting index i, and the inner loop extends the substring to the right as long as it remains valid. A substring is valid if its characters are consecutive in the alphabet (e.g., 'a' -> 'b', 'z' -> 'a').
If a substring is found to be valid, it's added to a HashSet<String>. The HashSet automatically handles uniqueness, ensuring that we don't count the same substring multiple times. Finally, the number of unique valid substrings is simply the size of the set.
This method is straightforward but computationally expensive due to the large number of substrings and the overhead of string operations and storage.
import java.util.HashSet;import java.util.Set; class Solution { public int findSubstringInWraproundString(String s) { if (s == null || s.length() == 0) { return 0; } Set<String> uniqueSubstrings = new HashSet<>(); for (int i = 0; i < s.length(); i++) { // Add single character substring uniqueSubstrings.add(s.substring(i, i + 1)); for (int j = i + 1; j < s.length(); j++) { char prev = s.charAt(j - 1); char curr = s.charAt(j); // Check for wraparound consecutiveness if ((curr - prev + 26) % 26 == 1) { uniqueSubstrings.add(s.substring(i, j + 1)); } else { break; // End of consecutive sequence } } } return uniqueSubstrings.size(); }}Complexity
Time
O(N^3), where N is the length of the string `s`. The two nested loops give a factor of `O(N^2)`. Inside the loop, creating and hashing a substring of length `L` takes `O(L)` time. Since `L` can be up to `N`, the total time is `O(N^3)`.
Space
O(N^3), where N is the length of `s`. In the worst case (e.g., `s` is `"abc..."`), the set would store `O(N^2)` unique substrings, and the total space required for these strings can be up to `O(N^3)`.
Trade-offs
Pros
Simple to understand and implement.
Directly follows the problem statement by generating, validating, and counting unique substrings.
Cons
Highly inefficient. Time complexity is at least
O(N^2)due to nested loops, and can be up toO(N^3)because of string manipulation (substring creation and hashing).High space complexity, as the
HashSetmight need to storeO(N^2)substrings, leading toO(N^3)space usage in the worst case.Will result in a 'Time Limit Exceeded' (TLE) error for the given constraints (
N <= 10^5).
This efficient approach avoids generating and storing all substrings. Instead, it leverages a key insight: if we have multiple valid substrings ending with the same character, say 'c', like "abc" and "bc", the shorter one ("bc") is always a suffix of the longer one ("abc"). Therefore, to count all unique substrings ending in 'c', we only need to find the length of the longest valid substring ending in 'c'. The total count is the sum of these maximum lengths for each character from 'a' to 'z'.
Algorithm
- Create an integer array
maxLengthof size 26, initialized to zeros. - Initialize an integer
currentLength = 0. - Iterate through the string
sfromi = 0tos.length() - 1. - If
i > 0ands.charAt(i)is consecutive tos.charAt(i-1)(with wraparound), incrementcurrentLength. - Otherwise, reset
currentLengthto 1. - Let
index = s.charAt(i) - 'a'. - Update
maxLength[index]with the maximum of its current value andcurrentLength:maxLength[index] = Math.max(maxLength[index], currentLength). - After the loop, initialize
totalCount = 0. - Iterate through the
maxLengtharray and add each element tototalCount. - Return
totalCount.
Walkthrough
The main idea is that the number of unique valid substrings is the sum of the lengths of the longest valid substrings ending with each character. For example, if the longest valid substring ending in 'c' is "abc" (length 3), it implies the existence of "c", "bc", and "abc". Any other valid substring ending in 'c' found elsewhere must be shorter than or equal to "abc" and thus is already accounted for.
We use an array, maxLength of size 26, to keep track of the maximum length of a valid substring ending with each letter of the alphabet. We iterate through the input string s once, maintaining a currentLength which represents the length of the valid substring ending at the current position i.
- If
s[i]ands[i-1]are consecutive, we incrementcurrentLength. - If they are not, the streak is broken, and we reset
currentLengthto 1. - At each step
i, we update themaxLengthfor the characters[i]:maxLength[s[i] - 'a'] = Math.max(maxLength[s[i] - 'a'], currentLength).
After iterating through the entire string, we sum up all the values in the maxLength array to get the total count.
class Solution { public int findSubstringInWraproundString(String s) { if (s == null || s.length() == 0) { return 0; } // maxLength[i] stores the length of the longest valid substring ending with character 'a' + i. int[] maxLength = new int[26]; int currentLength = 0; for (int i = 0; i < s.length(); i++) { // Check if current character is consecutive to the previous one if (i > 0 && (s.charAt(i) - s.charAt(i - 1) + 26) % 26 == 1) { currentLength++; } else { currentLength = 1; } int index = s.charAt(i) - 'a'; maxLength[index] = Math.max(maxLength[index], currentLength); } // The total number of unique substrings is the sum of the max lengths. int totalCount = 0; for (int length : maxLength) { totalCount += length; } return totalCount; }}Complexity
Time
O(N), where N is the length of the string `s`. We perform a single pass through the string, and all operations inside the loop are constant time. The final summation takes O(26), which is constant.
Space
O(1). We only use an auxiliary array of size 26, which is constant and does not depend on the input string's length.
Trade-offs
Pros
Extremely efficient with a linear time complexity.
Uses constant extra space, making it very memory-efficient.
Solves the problem with a single pass over the input string.
Cons
The logic might be less intuitive at first glance compared to the brute-force approach.
Solutions
Solution
class Solution {public int findSubstringInWraproundString(String p) { int[] dp = new int[26]; int k = 0; for (int i = 0; i < p.length(); ++i) { char c = p.charAt(i); if (i > 0 && (c - p.charAt(i - 1) + 26) % 26 == 1) { ++k; } else { k = 1; } dp[c - 'a'] = Math.max(dp[c - 'a'], k); } int ans = 0; for (int v : dp) { ans += v; } return ans; }}class Solution {public: int findSubstringInWraproundString(string p) { vector<int> dp(26); int k = 0; for (int i = 0; i < p.size(); ++i) { char c = p[i]; if (i && (c - p[i - 1] + 26) % 26 == 1) ++k; else k = 1; dp[c - 'a'] = max(dp[c - 'a'], k); } int ans = 0; for (int &v : dp) ans += v; return ans; }};class Solution: def findSubstringInWraproundString(self, p: str) -> int: dp = [0] * 26 k = 0 for i, c in enumerate(p): if i and (ord(c) - ord(p[i - 1])) % 26 == 1: k += 1 else: k = 1 idx = ord(c) - ord('a') dp[idx] = max(dp[idx], k) return sum(dp)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.