Facebook Pixel

3135. Equalize Strings by Adding or Removing Characters at Ends 🔒


Problem Description

Given two strings initial and target, your task is to modify initial by performing a series of operations to make it equal to target.

In one operation, you can add or remove one character only at the beginning or the end of the string initial.

Return the minimum number of operations required to transform initial into target.

Intuition

To solve this problem, we need to minimize the number of operations required to transform the string initial into the string target. The key insight is to find the longest common substring that can exist between initial and target, because by identifying the longest identical sequence present in both strings, we minimize changes needed on initial.

  1. Identify the Longest Common Substring: By using dynamic programming, we can determine the length of the longest common substring between initial and target. This substring represents the part of initial that is also present in target, requiring no changes.

  2. Calculate Minimum Operations: Once we have the length of this longest common substring, denoted as mx, the number of operations required to make initial into target can be calculated as:

    • We have to remove characters not part of the substring from initial, which costs m - mx operations (where m is the length of initial).
    • Similarly, we have to add characters to reconstruct target, which costs n - mx operations (where n is the length of target).
    • Therefore, the total operations required are: m + n - 2 * mx.

By leveraging dynamic programming to find the longest common substring, this approach efficiently leads us to the minimum number of operations required.

Learn more about Binary Search, Dynamic Programming and Sliding Window patterns.

Solution Approach

To solve the problem, a dynamic programming approach is employed to find the longest common substring between the two strings, initial and target. Here’s a detailed breakdown of the solution approach:

  1. Define the Dynamic Programming Table:

    • We use a two-dimensional array f, where f[i][j] represents the length of the longest common substring ending at initial[i - 1] and target[j - 1].
  2. Initialize the Table:

    • The f table is initialized to zero. This size is (m+1) x (n+1), where m is the length of initial and n is the length of target.
  3. State Transition:

    • If the characters initial[i-1] and target[j-1] are the same, then f[i][j] = f[i-1][j-1] + 1. This captures the continuation of a common substring.
    • Otherwise, f[i][j] = 0.
  4. Track Maximum Length:

    • We maintain a variable mx to keep track of the maximum value found in the f table, which represents the length of the longest common substring.
  5. Calculate Minimum Operations:

    • The minimum number of operations required to transform initial into target is given by the formula: m + n - 2 * mx.

Here is the implementation in code:

class Solution:
    def minOperations(self, initial: str, target: str) -> int:
        m, n = len(initial), len(target)
        f = [[0] * (n + 1) for _ in range(m + 1)]
        mx = 0
        for i, a in enumerate(initial, 1):
            for j, b in enumerate(target, 1):
                if a == b:
                    f[i][j] = f[i - 1][j - 1] + 1
                    mx = max(mx, f[i][j])
        return m + n - mx * 2

This code effectively finds the longest common substring length and computes the transformations needed by leveraging dynamic programming.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through an example to illustrate how the solution approach works.

Example:

  • initial = "abcde"
  • target = "acd"

Step 1: Define the Dynamic Programming Table

We create a dynamic programming table f with dimensions (m+1) x (n+1), where m = 5 (length of "abcde") and n = 3 (length of "acd"). This table is initially filled with zeros:

    ""  a   c   d
""  0   0   0   0
a   0   0   0   0
b   0   0   0   0
c   0   0   0   0
d   0   0   0   0
e   0   0   0   0

Step 2: State Transition

We iterate over each character pair (initial[i-1], target[j-1]) and update the table:

  • When i=1, a="a", matching j=1, b="a":

    • f[1][1] = f[0][0] + 1 = 1
    • Update the table:
         ""  a   c   d
      ""  0   0   0   0
      a   0   1   0   0
      b   0   0   0   0
      c   0   0   0   0
      d   0   0   0   0
      e   0   0   0   0
  • When i=3, a="c", matching j=2, b="c":

    • f[3][2] = f[2][1] + 1 = 1
    • Update the table:
         ""  a   c   d
      ""  0   0   0   0
      a   0   1   0   0
      b   0   0   0   0
      c   0   0   1   0
      d   0   0   0   0
      e   0   0   0   0
  • When i=4, a="d", matching j=3, b="d":

    • f[4][3] = f[3][2] + 1 = 2
    • Update the table:
         ""  a   c   d
      ""  0   0   0   0
      a   0   1   0   0
      b   0   0   0   0
      c   0   0   1   0
      d   0   0   0   2
      e   0   0   0   0

Step 3: Track Maximum Length

We find the maximum value in the table, mx = 2, which is the longest common substring length.

Step 4: Calculate Minimum Operations

Calculate the required operations:

  • Length of initial (m) = 5
  • Length of target (n) = 3
  • mx = 2

The formula is m + n - 2 * mx:

  • 5 + 3 - 2 * 2 = 4

So, the minimum number of operations needed is 4.

This walkthrough demonstrates finding the longest common substring using dynamic programming and calculating the minimum operations needed to transform initial into target.

Solution Implementation

1class Solution:
2    def minOperations(self, initial: str, target: str) -> int:
3        # Lengths of the initial and target strings
4        m, n = len(initial), len(target)
5
6        # Initialize a 2D list (matrix) to compute the longest common subsequence
7        # The size is (m+1) x (n+1) with default values of 0
8        f = [[0] * (n + 1) for _ in range(m + 1)]
9
10        # Variable to track the maximum length of common subsequence seen so far
11        max_length = 0
12
13        # Loop through each character of the initial string with index starting from 1
14        for i, char_initial in enumerate(initial, 1):
15            # Loop through each character of the target string with index starting from 1
16            for j, char_target in enumerate(target, 1):
17                # If the characters match, update the matrix at f[i][j]
18                # by adding 1 to the value at the diagonal position f[i-1][j-1]
19                if char_initial == char_target:
20                    f[i][j] = f[i - 1][j - 1] + 1
21                    # Update the maximum length if current subsequence is greater
22                    max_length = max(max_length, f[i][j])
23      
24        # Return the minimum number of operations required to make the strings equal
25        # This is calculated by subtracting twice the maximum common subsequence length
26        # from the sum of the lengths of the two strings
27        return m + n - max_length * 2
28
1class Solution {
2    public int minOperations(String initial, String target) {
3        // Get lengths of the initial and target strings
4        int lengthInitial = initial.length();
5        int lengthTarget = target.length();
6      
7        // Create a 2D array for dynamic programming, initialized to 0
8        int[][] commonSubstringLength = new int[lengthInitial + 1][lengthTarget + 1];
9      
10        // Variable to store the maximum length of common subsequence found
11        int maxCommonLength = 0;
12      
13        // Fill the array using dynamic programming approach
14        for (int i = 1; i <= lengthInitial; ++i) {
15            for (int j = 1; j <= lengthTarget; ++j) {
16                // Check if characters current in both strings match
17                if (initial.charAt(i - 1) == target.charAt(j - 1)) {
18                    // If match, extend the length of common substring by 1
19                    commonSubstringLength[i][j] = commonSubstringLength[i - 1][j - 1] + 1;
20                    // Update the maximum common length found so far
21                    maxCommonLength = Math.max(maxCommonLength, commonSubstringLength[i][j]);
22                }
23            }
24        }
25      
26        // Calculate minimum operations needed
27        return lengthInitial + lengthTarget - 2 * maxCommonLength;
28    }
29}
30
1#include <string>
2#include <vector>
3#include <cstring>
4#include <algorithm>
5
6class Solution {
7public:
8    int minOperations(std::string initial, std::string target) {
9        int m = initial.size();  // Length of the initial string
10        int n = target.size();   // Length of the target string
11
12        // Dynamic programming table to store lengths of longest common subsequences
13        int dp[m + 1][n + 1];    
14        std::memset(dp, 0, sizeof(dp));  // Initialize the dp array with zero
15
16        int maxLength = 0;  // Store the length of the longest common subsequence
17
18        // Populate the dp table
19        for (int i = 1; i <= m; ++i) {
20            for (int j = 1; j <= n; ++j) {
21                // If characters match, increment the length of LCS by 1
22                if (initial[i - 1] == target[j - 1]) {
23                    dp[i][j] = dp[i - 1][j - 1] + 1;
24                    maxLength = std::max(maxLength, dp[i][j]);  // Update the maxLength
25                }
26            }
27        }
28
29        // Minimum operations needed is the total length of both strings minus two times the length of longest common subsequence
30        return m + n - 2 * maxLength;
31    }
32};
33
1function minOperations(initial: string, target: string): number {
2    // Get lengths of input strings
3    const initialLength = initial.length;
4    const targetLength = target.length;
5
6    // Create a 2D array to store lengths of common subsequences
7    const dp: number[][] = Array.from({ length: initialLength + 1 }, () => Array(targetLength + 1).fill(0));
8
9    // Variable to track the maximum length of common subsequence found
10    let maxLengthCommonSubseq: number = 0;
11
12    // Loop over each character of initial and target strings
13    for (let i = 1; i <= initialLength; ++i) {
14        for (let j = 1; j <= targetLength; ++j) {
15            // If characters match, increment the length of common subsequence
16            if (initial[i - 1] === target[j - 1]) {
17                dp[i][j] = dp[i - 1][j - 1] + 1;
18                maxLengthCommonSubseq = Math.max(maxLengthCommonSubseq, dp[i][j]);
19            }
20        }
21    }
22
23    // Calculate the minimum number of operations required
24    return initialLength + targetLength - 2 * maxLengthCommonSubseq;
25}
26

Time and Space Complexity

The time complexity is O(m * n), as the algorithm processes every element of the initial string in conjunction with every element of the target string to compute the longest common subsequence length. This nested iteration results in m * n operations.

The space complexity is O(m * n), since a 2D list f of size (m+1) * (n+1) is utilized to store intermediate results of the longest common subsequence computation, where m and n are the lengths of the strings initial and target, respectively.

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

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

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


Load More