Select K Disjoint Special Substrings
MedPrompt
Given a string s of length n and an integer k, determine whether it is possible to select k disjoint special substrings.
A special substring is a substring where:
- Any character present inside the substring should not appear outside it in the string.
- The substring is not the entire string
s.
Note that all k substrings must be disjoint, meaning they cannot overlap.
Return true if it is possible to select k such disjoint special substrings; otherwise, return false.
Example 1:
Input: s = "abcdbaefab", k = 2
Output: true
Explanation:
- We can select two disjoint special substrings:
"cd"and"ef". "cd"contains the characters'c'and'd', which do not appear elsewhere ins."ef"contains the characters'e'and'f', which do not appear elsewhere ins.
Example 2:
Input: s = "cdefdc", k = 3
Output: false
Explanation:
There can be at most 2 disjoint special substrings: "e" and "f". Since k = 3, the output is false.
Example 3:
Input: s = "abeabe", k = 0
Output: true
Constraints:
2 <= n == s.length <= 5 * 1040 <= k <= 26sconsists only of lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
A standard approach for problems asking for a maximum number of objects satisfying certain properties is dynamic programming. We can define a DP state based on the prefixes of the string.
Algorithm
Let dp[i] be the maximum number of disjoint special substrings in the prefix s[0...i-1].
Precompute the first and last occurrence index for each character in s.
Initialize dp array of size n+1 to all zeros.
Iterate i from 1 to n to compute dp[i]:
Initialize dp[i] = dp[i-1] (option to not end a substring at i-1).
Iterate j from i-1 down to 0 (potential start of a substring s[j...i-1]):
Keep track of min_first and max_last for characters in s[j...i-1].
If min_first >= j and max_last < i, then s[j...i-1] is a special substring.
If it is special (and not the whole string), update dp[i] = max(dp[i], (j > 0 ? dp[j] : 0) + 1).
After the loops, dp[n] holds the maximum number of disjoint special substrings.
Return dp[n] >= k.
Walkthrough
Let dp[i] be the maximum number of disjoint special substrings that can be formed entirely within the prefix s[0...i-1].
Our goal is to compute dp[n] and check if it's greater than or equal to k.
The base case is dp[0] = 0, as no substrings can be formed from an empty prefix.
For the transition, to compute dp[i], we have two choices:
- We don't form a special substring ending at index
i-1. In this case, the maximum number of substrings is the same as for the prefixs[0...i-2]. So,dp[i] = dp[i-1]. - We form a special substring ending at index
i-1. Let this substring bes[j...i-1]for some0 <= j < i. Ifs[j...i-1]is a valid special substring, we can potentially form one more substring than the maximum we could form in the prefixs[0...j-1]. This would givedp[j] + 1substrings. We should try this for all possible start indicesj.
Combining these, the recurrence relation is:
dp[i] = max(dp[i-1], max(dp[j] + 1)) for all j such that s[j...i-1] is a special substring.
To implement this, we first need an efficient way to check if a substring s[j...i-1] is special. A substring is special if every character within it does not appear outside of it. This means for every character c in s[j...i-1], its first and last occurrences in the original string s must be within the bounds [j, i-1].
We can precompute the first and last occurrences of all characters in s in O(n) time. Let's call them first[c] and last[c].
The check for s[j...i-1] being special becomes:
min(first[s[p]]) >= jforpfromjtoi-1.max(last[s[p]]) <= i-1forpfromjtoi-1.- The substring is not the entire string
s.
The overall algorithm is:
- Precompute
firstandlastarrays for all 26 lowercase letters. O(n). - Initialize a
dparray of sizen+1with zeros. - Iterate
ifrom 1 ton: a. Setdp[i] = dp[i-1]. b. Iteratejfromi-1down to0: i. Check ifs[j...i-1]is a special substring. This check involves iterating fromjtoi-1to find the minimum offirstoccurrences and maximum oflastoccurrences for the characters in the substring. This can be optimized by updating these min/max values asjdecreases. ii. Ifs[j...i-1]is special (and not the whole string), updatedp[i] = max(dp[i], dp[j] + 1). - Finally, return
dp[n] >= k.
class Solution { public boolean canSelect(String s, int k) { if (k == 0) { return true; } int n = s.length(); int[] first = new int[26]; int[] last = new int[26]; java.util.Arrays.fill(first, -1); java.util.Arrays.fill(last, -1); for (int i = 0; i < n; i++) { int charIndex = s.charAt(i) - 'a'; if (first[charIndex] == -1) { first[charIndex] = i; } last[charIndex] = i; } int[] dp = new int[n + 1]; for (int i = 1; i <= n; i++) { dp[i] = dp[i - 1]; int minFirst = n; int maxLast = -1; for (int j = i - 1; j >= 0; j--) { int charIndex = s.charAt(j) - 'a'; minFirst = Math.min(minFirst, first[charIndex]); maxLast = Math.max(maxLast, last[charIndex]); if (minFirst >= j && maxLast < i) { if (j == 0 && i == n) continue; // Not the entire string dp[i] = Math.max(dp[i], (j > 0 ? dp[j] : 0) + 1); } } } return dp[n] >= k; }}Complexity
Time
O(n^2), where n is the length of the string. The nested loops for the DP calculation dominate the complexity. The outer loop runs `n` times, and the inner loop runs up to `n` times.
Space
O(n) for the `dp` array. The `first` and `last` arrays take O(1) space as the alphabet size is constant (26).
Trade-offs
Pros
Correctly solves the problem for all cases.
Conceptually straightforward application of dynamic programming.
Cons
The time complexity is too high for the given constraints.
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.