Facebook Pixel

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 of targetIndices.
  • pattern remains a subsequence of source 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:

  1. 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.

  2. Transition Logic:

    • Skipping a character: If you skip the i-th character of source, you can inherit the deletions from the previous character, i.e., f[i][j] = f[i-1][j]. Additionally, if i-1 is in targetIndices, increment the count by one deletion.
    • Matching a character: If the current character in source matches the corresponding character in pattern, you can choose this character to match, leading to f[i][j] = max(f[i][j], f[i-1][j-1]).
  3. Result: The value f[m][n] (where m and n are lengths of source and pattern, 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:

  1. Define the DP Table:

    • We define a 2D list f where f[i][j] denotes the maximum number of deletions allowed in the first i characters of source while preserving the first j characters of the pattern.
  2. Initialization:

    • Start with f[0][0] = 0 since initially there are no characters to remove and pattern is trivially a subsequence.
    • All other entries in f are initialized to -∞, representing that those states are not achievable without any operations.
  3. Iterate over the source:

    • For each character c at index i-1 in source, iterate through each j from 0 to n (inclusive), where n is the length of pattern.
  4. Two Main Choices in Transition:

    • Skip the character: If we decide to skip the i-th character of source, add potential deletions from previous state: f[i][j] = f[i-1][j] + int((i - 1) in s) where s is the set of targetIndices.
    • 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.
  5. Final Computation:

    • After processing all characters of source, the solution to the problem is found at f[m][n], where m is the length of source and n is the length of pattern.

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 Evaluator

Example 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:

  1. Initialization:

    • Create a DP table f where f[i][j] tracks the maximum deletions in the first i characters of source while preserving the first j characters of pattern.
    • 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 -∞.
  2. Iterate Over the source:

    isource[i-1]Explanation
    1'a'Matches pattern[0], update f[1][1] = f[0][0] = 0 (since we can use 'a')
    2'b'Can skip 'b', since it's in targetIndices, update f[2][0] = f[1][0] + 1 = 1 (one deletion because 'b' is removable)
    3'c'Matches pattern[1], update f[3][2] = f[2][1] = 0 (use 'c' for matching)
    4'd'Can skip 'd', since it's in targetIndices, update f[4][2] = f[3][2] + 1 = 1 (second deletion of 'd')
    5'e'Matches pattern[2], update f[5][3] = f[4][2] = 1 (use 'e' for matching)
  3. 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 maintaining pattern as a subsequence of source.

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.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

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

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More