3316. Find Maximum Removals From Source String
Problem Description
You are given a string source
of size n
, a string pattern
that is a subsequence of source
, and a sorted integer array targetIndices
that contains distinct numbers in the range [0, n - 1]
.
An operation is defined as removing a character at an index idx
from source
such that:
idx
is an element oftargetIndices
.pattern
remains a subsequence ofsource
after removing the character.
Performing an operation does not change the indices of the other characters in source
. For example, if you remove 'c'
from "acb"
, the character at index 2 would still be 'b'
.
Your task is to determine the maximum number of operations that can be performed.
Intuition
The goal is to maximize the number of characters we can remove from the source
string while still maintaining pattern
as a subsequence. One effective way to solve this problem is by using dynamic programming (DP).
The idea is to use a 2D DP table f[i][j]
, where f[i][j]
represents the maximum number of deletions possible in the first i
characters of source
while matching the first j
characters of pattern
.
Here's the approach:
-
Initialization: Start with
f[0][0] = 0
, which means no deletions have been made initially, and all other entries can be set to-∞
because they are not achievable without operations. -
Transition Logic:
- Skipping a character: If you skip the
i-th
character ofsource
, you can inherit the deletions from the previous character, i.e.,f[i][j] = f[i-1][j]
. Additionally, ifi-1
is intargetIndices
, increment the count by one deletion. - Matching a character: If the current character in
source
matches the corresponding character inpattern
, you can choose this character to match, leading tof[i][j] = max(f[i][j], f[i-1][j-1])
.
- Skipping a character: If you skip the
-
Result: The value
f[m][n]
(wherem
andn
are lengths ofsource
andpattern
, respectively) will provide the maximum number of deletions that can be made without disturbing the subsequence relationship.
This approach effectively balances maintaining the subsequence and making the maximum allowable deletions.
Learn more about Two Pointers and Dynamic Programming patterns.
Solution Approach
To solve this problem, we employ a dynamic programming approach. Here is a step-by-step explanation of the solution:
-
Define the DP Table:
- We define a 2D list
f
wheref[i][j]
denotes the maximum number of deletions allowed in the firsti
characters ofsource
while preserving the firstj
characters of thepattern
.
- We define a 2D list
-
Initialization:
- Start with
f[0][0] = 0
since initially there are no characters to remove andpattern
is trivially a subsequence. - All other entries in
f
are initialized to-∞
, representing that those states are not achievable without any operations.
- Start with
-
Iterate over the
source
:- For each character
c
at indexi-1
insource
, iterate through eachj
from 0 ton
(inclusive), wheren
is the length ofpattern
.
- For each character
-
Two Main Choices in Transition:
- Skip the character: If we decide to skip the
i-th
character ofsource
, add potential deletions from previous state:f[i][j] = f[i-1][j] + int((i - 1) in s)
wheres
is the set oftargetIndices
. - Match the character: If
source[i-1] == pattern[j-1]
, we can use this character to match, therefore,f[i][j] = max(f[i][j], f[i-1][j-1])
, indicating no change in the number of deletions.
- Skip the character: If we decide to skip the
-
Final Computation:
- After processing all characters of
source
, the solution to the problem is found atf[m][n]
, wherem
is the length ofsource
andn
is the length ofpattern
.
- After processing all characters of
By considering both possibilities at each step—whether to skip or match a character—and updating the DP table accordingly, we efficiently compute the maximum number of deletions that can be made while keeping pattern
as a subsequence of source
.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Consider the following small example to illustrate the solution approach:
Suppose you have source = "abcde"
, pattern = "ace"
, and targetIndices = [0, 1, 3]
.
Step-by-Step Walkthrough:
-
Initialization:
- Create a DP table
f
wheref[i][j]
tracks the maximum deletions in the firsti
characters ofsource
while preserving the firstj
characters ofpattern
. - Start with
f[0][0] = 0
, meaning no deletions have been performed initially. - All other entries in the first row
f[0][j]
are initialized to-∞
.
- Create a DP table
-
Iterate Over the
source
:i source[i-1]
Explanation 1 'a'
Matches pattern[0]
, updatef[1][1] = f[0][0]
= 0 (since we can use'a'
)2 'b'
Can skip 'b'
, since it's intargetIndices
, updatef[2][0] = f[1][0] + 1
= 1 (one deletion because'b'
is removable)3 'c'
Matches pattern[1]
, updatef[3][2] = f[2][1]
= 0 (use'c'
for matching)4 'd'
Can skip 'd'
, since it's intargetIndices
, updatef[4][2] = f[3][2] + 1
= 1 (second deletion of'd'
)5 'e'
Matches pattern[2]
, updatef[5][3] = f[4][2]
= 1 (use'e'
for matching) -
Final Computation:
After processing all characters, the value
f[5][3] = 1
gives us the maximum number of operations (deletions) that can be performed while maintainingpattern
as a subsequence ofsource
.
In this example, we've removed two characters from source
: 'b'
at index 1 and 'd'
at index 3, while ensuring that pattern
"ace"
remains a valid subsequence of source
. The resulting string is "ace"
, and we achieved one deletion operation following the rule set.
Solution Implementation
1from typing import List
2
3class Solution:
4 def maxRemovals(self, source: str, pattern: str, targetIndices: List[int]) -> int:
5 # Length of source and pattern strings
6 m, n = len(source), len(pattern)
7
8 # Initialize a 2D list with negative infinity for dynamic programming
9 # f[i][j] represents the maximum removals up to source[i-1] with pattern[j-1]
10 f = [[float('-inf')] * (n + 1) for _ in range(m + 1)]
11
12 # Base case: No removals needed when both source and pattern are empty
13 f[0][0] = 0
14
15 # Convert targetIndices to a set for O(1) lookup
16 removable_indices_set = set(targetIndices)
17
18 # Iterate through the source string with an index offset by 1
19 for i, char in enumerate(source, 1):
20 # Check every possible state of the pattern
21 for j in range(n + 1):
22 # Propagate the previous state of no match (if current index is in targetIndices set)
23 f[i][j] = f[i - 1][j] + int((i - 1) in removable_indices_set)
24
25 # If characters match and j > 0, consider the state of a successful match
26 if j > 0 and char == pattern[j - 1]:
27 # Take max of moving this matched character to the pattern sequence
28 f[i][j] = max(f[i][j], f[i - 1][j - 1])
29
30 # Return maximum number of characters that can be removed
31 return f[m][n]
32
1import java.util.Arrays;
2
3class Solution {
4 public int maxRemovals(String source, String pattern, int[] targetIndices) {
5 int sourceLength = source.length();
6 int patternLength = pattern.length();
7
8 // Initialize a 2D array for dynamic programming with dimensions (sourceLength + 1) x (patternLength + 1)
9 int[][] dpTable = new int[sourceLength + 1][patternLength + 1];
10 final int infinity = Integer.MAX_VALUE / 2; // Use half of Integer.MAX_VALUE to avoid overflow
11
12 // Fill the dpTable with negative infinity initially
13 for (var dpRow : dpTable) {
14 Arrays.fill(dpRow, -infinity);
15 }
16 dpTable[0][0] = 0; // Base case: no characters matched, zero removals
17
18 // Create an array to mark removable characters in the source
19 int[] removable = new int[sourceLength];
20 for (int index : targetIndices) {
21 removable[index] = 1;
22 }
23
24 // Fill the dpTable using dynamic programming
25 for (int i = 1; i <= sourceLength; ++i) {
26 for (int j = 0; j <= patternLength; ++j) {
27 // Case 1: Do not include current source character in matching pattern
28 dpTable[i][j] = dpTable[i - 1][j] + removable[i - 1];
29
30 // Case 2: Include current source character to potentially match the current pattern character
31 if (j > 0 && source.charAt(i - 1) == pattern.charAt(j - 1)) {
32 dpTable[i][j] = Math.max(dpTable[i][j], dpTable[i - 1][j - 1]);
33 }
34 }
35 }
36
37 return dpTable[sourceLength][patternLength]; // Result is the maximum number of valid character removals
38 }
39}
40
1class Solution {
2public:
3 int maxRemovals(string source, string pattern, vector<int>& targetIndices) {
4 int m = source.length(), n = pattern.length();
5
6 // Initialize a 2D vector for dynamic programming, f[i][j] indicates
7 // the maximum number of marked indices we can have in the first i
8 // characters of source that form the first j characters of pattern.
9 vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MIN / 2));
10
11 // Base case: forming an empty pattern requires zero indices removed
12 dp[0][0] = 0;
13
14 // Mark the indices to be removed according to targetIndices
15 vector<int> marked(m, 0);
16 for (int index : targetIndices) {
17 marked[index] = 1; // Mark the index as removed
18 }
19
20 // Iterate through each character of source
21 for (int i = 1; i <= m; ++i) {
22 for (int j = 0; j <= n; ++j) {
23 // Calculate the maximum removals if we're not matching
24 // characters of pattern, accruing the removal value
25 dp[i][j] = dp[i - 1][j] + marked[i - 1];
26
27 // If the current characters of source and pattern match,
28 // consider matching them as a potential optimal strategy
29 if (j > 0 && source[i - 1] == pattern[j - 1]) {
30 dp[i][j] = max(dp[i][j], dp[i - 1][j - 1]);
31 }
32 }
33 }
34
35 // Return the maximum removals possible to form the pattern from source
36 return dp[m][n];
37 }
38};
39
1// Function to determine the maximum number of removable characters
2// from the source string while maintaining pattern in the modified string.
3function maxRemovals(source: string, pattern: string, targetIndices: number[]): number {
4 const m = source.length; // Length of the source string
5 const n = pattern.length; // Length of the pattern string
6
7 // Initialize a 2D array to hold results of subproblems, filled with negative infinity initially
8 const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(-Infinity));
9 dp[0][0] = 0; // Base case: no characters removed from both strings
10
11 // Array to determine removable characters from the source string
12 const removable: number[] = Array(m).fill(0);
13
14 // Mark indices from targetIndices array as removable
15 for (const index of targetIndices) {
16 removable[index] = 1;
17 }
18
19 // Iterate through the source string
20 for (let i = 1; i <= m; i++) {
21 // Iterate through pattern string
22 for (let j = 0; j <= n; j++) {
23 // Consider the scenario where character at i-1 is removed
24 dp[i][j] = dp[i - 1][j] + removable[i - 1];
25
26 // If characters from source and pattern match, consider keeping the character
27 if (j > 0 && source[i - 1] === pattern[j - 1]) {
28 dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1]);
29 }
30 }
31 }
32
33 // Return the maximum number of removable characters while still fully matching the pattern
34 return dp[m][n];
35}
36
Time and Space Complexity
The time complexity of the code is O(m * n)
because it consists of two nested loops iterating over the length m
of source
and n
of pattern
. For each i
from 1
to m
, the inner loop iterates over j
from 0
to n
, performing constant-time operations inside.
The space complexity is also O(m * n)
due to the creation of a 2D list f
with dimensions (m + 1) x (n + 1)
. Each entry in this list stores an integer, representing the dynamic programming state.
Learn more about how to find time and space complexity quickly.
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
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
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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!