1898. Maximum Number of Removable Characters
Problem Description
You have two strings s and p, where p is a subsequence of s. You also have a distinct array removable containing indices of characters in s (using 0-based indexing).
Your task is to find the maximum number k such that after removing the first k characters indicated by the indices in removable from string s, the string p remains a subsequence of the modified s.
Here's how the removal process works:
- You choose a value
kwhere0 <= k <= removable.length - You mark the characters at positions
s[removable[0]],s[removable[1]], ...,s[removable[k-1]] - You remove all marked characters from
s - You check if
pis still a subsequence of the remaining string
A subsequence means that p can be formed from s by deleting some (or no) characters without changing the order of the remaining characters.
For example, if s = "abcab", p = "ab", and removable = [0, 1, 2]:
- If
k = 0: no removals,pis still a subsequence ofs - If
k = 1: removes[0]('a'), resulting in "bcab",pis still a subsequence - If
k = 2: removes[0]ands[1]('a' and 'b'), resulting in "cab",pis still a subsequence - If
k = 3: removes[0],s[1], ands[2]('a', 'b', and 'c'), resulting in "ab",pis still a subsequence
The goal is to find the largest possible k value that maintains p as a subsequence of the modified s.
Intuition
The key observation is that this problem has a monotonic property: if removing the first k characters from removable still keeps p as a subsequence of s, then removing any fewer characters will also keep p as a subsequence. Conversely, if removing k characters breaks the subsequence relationship, then removing more characters will also break it.
This monotonic behavior creates a sequence of feasibility values:
Removals k: 0 1 2 3 4 5 6 Feasible: T T T T F F F ↑ first_true_index (searching from right)
We want to find the maximum k where p remains a subsequence. Using our standard binary search template, we search for the last true value - the maximum feasible k.
For each candidate k, we define feasible(k) as: after removing characters at indices removable[0] through removable[k-1], is p still a subsequence of s? We check this using two pointers to match characters of p against the non-removed characters of s.
The search range is from 0 (remove nothing) to len(removable) (remove all), and we track the maximum feasible value with first_true_index.
Learn more about Two Pointers and Binary Search patterns.
Solution Implementation
1class Solution:
2 def maximumRemovals(self, s: str, p: str, removable: List[int]) -> int:
3 def feasible(k: int) -> bool:
4 """Check if p is still a subsequence after removing first k indices."""
5 is_removed = [False] * len(s)
6 for i in range(k):
7 is_removed[removable[i]] = True
8
9 # Two pointers to check subsequence
10 j = 0
11 for i in range(len(s)):
12 if not is_removed[i] and j < len(p) and s[i] == p[j]:
13 j += 1
14 return j == len(p)
15
16 # Binary search with standard template
17 left, right = 0, len(removable)
18 first_true_index = 0
19
20 while left <= right:
21 mid = (left + right) // 2
22
23 if feasible(mid):
24 first_true_index = mid
25 left = mid + 1
26 else:
27 right = mid - 1
28
29 return first_true_index
301class Solution {
2 private char[] source;
3 private char[] pattern;
4 private int[] removable;
5
6 public int maximumRemovals(String s, String p, int[] removable) {
7 this.source = s.toCharArray();
8 this.pattern = p.toCharArray();
9 this.removable = removable;
10
11 // Binary search with standard template
12 int left = 0;
13 int right = removable.length;
14 int firstTrueIndex = 0;
15
16 while (left <= right) {
17 int mid = left + (right - left) / 2;
18
19 if (feasible(mid)) {
20 firstTrueIndex = mid;
21 left = mid + 1;
22 } else {
23 right = mid - 1;
24 }
25 }
26
27 return firstTrueIndex;
28 }
29
30 private boolean feasible(int k) {
31 boolean[] isRemoved = new boolean[source.length];
32 for (int i = 0; i < k; i++) {
33 isRemoved[removable[i]] = true;
34 }
35
36 // Two pointers to check subsequence
37 int j = 0;
38 for (int i = 0; i < source.length && j < pattern.length; i++) {
39 if (!isRemoved[i] && source[i] == pattern[j]) {
40 j++;
41 }
42 }
43 return j == pattern.length;
44 }
45}
461class Solution {
2public:
3 int maximumRemovals(string s, string p, vector<int>& removable) {
4 int n = s.size();
5 int m = p.size();
6
7 auto feasible = [&](int k) {
8 vector<bool> isRemoved(n, false);
9 for (int i = 0; i < k; i++) {
10 isRemoved[removable[i]] = true;
11 }
12
13 // Two pointers to check subsequence
14 int j = 0;
15 for (int i = 0; i < n && j < m; i++) {
16 if (!isRemoved[i] && s[i] == p[j]) {
17 j++;
18 }
19 }
20 return j == m;
21 };
22
23 // Binary search with standard template
24 int left = 0;
25 int right = removable.size();
26 int firstTrueIndex = 0;
27
28 while (left <= right) {
29 int mid = left + (right - left) / 2;
30
31 if (feasible(mid)) {
32 firstTrueIndex = mid;
33 left = mid + 1;
34 } else {
35 right = mid - 1;
36 }
37 }
38
39 return firstTrueIndex;
40 }
41};
421function maximumRemovals(s: string, p: string, removable: number[]): number {
2 const feasible = (k: number): boolean => {
3 const isRemoved: boolean[] = new Array(s.length).fill(false);
4 for (let i = 0; i < k; i++) {
5 isRemoved[removable[i]] = true;
6 }
7
8 // Two pointers to check subsequence
9 let j = 0;
10 for (let i = 0; i < s.length && j < p.length; i++) {
11 if (!isRemoved[i] && s[i] === p[j]) {
12 j++;
13 }
14 }
15 return j === p.length;
16 };
17
18 // Binary search with standard template
19 let left = 0;
20 let right = removable.length;
21 let firstTrueIndex = 0;
22
23 while (left <= right) {
24 const mid = Math.floor((left + right) / 2);
25
26 if (feasible(mid)) {
27 firstTrueIndex = mid;
28 left = mid + 1;
29 } else {
30 right = mid - 1;
31 }
32 }
33
34 return firstTrueIndex;
35}
36Solution Approach
The solution uses binary search with the standard template, combined with a subsequence checking function.
Template Structure:
left, right = 0, len(removable) first_true_index = 0 # Default: we can remove 0 characters while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid # Record this feasible k left = mid + 1 # Try larger k else: right = mid - 1 # Try smaller k return first_true_index
The Feasibility Function:
For a given value k, we verify if p remains a subsequence of s after removing the first k characters:
- Create a boolean array to mark which positions are removed
- Mark positions
removable[0]throughremovable[k-1]as removed - Use two pointers to check if
pis a subsequence:- Pointer
itraverses strings - Pointer
jtraverses stringp - If
s[i]is not removed and matchesp[j], incrementj - Always increment
i
- Pointer
- Return
Trueifj == len(p)(all characters matched)
Binary Search Logic:
- If feasible: Record
first_true_index = midand search right (left = mid + 1) - If not feasible: Search left (
right = mid - 1)
Time Complexity: O(n × log m) where n is the length of s and m is the length of removable.
Space Complexity: O(n) for the boolean array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through with s = "abcbddddd", p = "abcd", and removable = [3, 2, 1, 4, 5, 6].
Step 1: Initialize
left = 0,right = 6(length of removable)first_true_index = 0
Step 2: Binary search iterations
Iteration 1: left = 0, right = 6
mid = (0 + 6) // 2 = 3- Remove first 3 indices: positions 3, 2, 1
- String becomes:
a _ _ _ d d d d d→ "addddd" - Is "abcd" a subsequence? Can match 'a', but can't find 'b' ✗
- Update:
right = 3 - 1 = 2
Iteration 2: left = 0, right = 2
mid = (0 + 2) // 2 = 1- Remove first 1 index: position 3
- String becomes:
a b c _ d d d d d→ "abcddddd" - Is "abcd" a subsequence? Match a→b→c→d ✓
- Update:
first_true_index = 1,left = 1 + 1 = 2
Iteration 3: left = 2, right = 2
mid = (2 + 2) // 2 = 2- Remove first 2 indices: positions 3 and 2
- String becomes:
a b _ _ d d d d d→ "abddddd" - Is "abcd" a subsequence? Match a→b, but can't find 'c' ✗
- Update:
right = 2 - 1 = 1
Step 3: Loop terminates
left = 2 > right = 1- Return
first_true_index = 1
We can remove at most 1 character while keeping p as a subsequence.
Time and Space Complexity
The time complexity is O(k × n × log k), where n is the length of string s and k is the length of removable.
- The binary search performs
O(log k)iterations since we're searching in the range[0, len(removable)] - For each binary search iteration, we call the
checkfunction - The
checkfunction:- Creates a boolean array
remof sizenand marksmidpositions:O(n + mid)wheremid ≤ k - Performs a two-pointer traversal through string
sto verify ifpis a subsequence:O(n) - Overall
checkfunction:O(n)
- Creates a boolean array
- Total time complexity:
O(log k) × O(n) = O(n × log k)
However, the reference answer states O(k × log k). This appears to be considering a different perspective or possibly an error. The actual complexity should be O(n × log k) based on the code analysis.
The space complexity is O(n), where n is the length of string s.
- The
remboolean array in thecheckfunction usesO(n)space - Other variables use constant space
- Total space complexity:
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using the Wrong Binary Search Template
The Pitfall: Using while left < right with left = mid instead of the standard while left <= right template.
Why It Happens: The left < right with left = mid variant requires mid = (left + right + 1) // 2 to avoid infinite loops. The standard template is simpler and less error-prone.
Solution: Use the standard template:
left, right = 0, len(removable)
first_true_index = 0
while left <= right:
mid = (left + right) // 2
if feasible(mid):
first_true_index = mid
left = mid + 1
else:
right = mid - 1
return first_true_index
2. Off-by-One Error in Binary Search Range
The Pitfall: Using len(removable) - 1 as upper bound.
Solution: The range should be [0, len(removable)] because we can remove 0 to all indices.
3. Inefficient Subsequence Checking
The Pitfall: Creating a new string after removals.
Solution: Use a boolean array to mark removed positions and check subsequence in-place:
is_removed = [False] * len(s)
for i in range(k):
is_removed[removable[i]] = True
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is
[3, 7, 40, 80].
What is the recurrence relation?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!