712. Minimum ASCII Delete Sum for Two Strings
Problem Description
You are given two strings s1 and s2. Your task is to delete some characters from both strings so that the resulting strings become equal. When you delete a character, you incur a cost equal to its ASCII value. You need to find the minimum total ASCII sum of all deleted characters required to make the two strings equal.
For example, if s1 = "sea" and s2 = "eat", you could delete 's' from s1 (ASCII value 115) and delete 'a' from s2 (ASCII value 97) to make both strings equal to "ea". The total cost would be 115 + 97 = 212. However, there might be other ways to delete characters that result in a lower total cost.
The solution uses dynamic programming where f[i][j] represents the minimum ASCII sum needed to make the first i characters of s1 equal to the first j characters of s2. The algorithm builds up the solution by considering three cases:
- If characters match (
s1[i-1] == s2[j-1]), no deletion is needed, sof[i][j] = f[i-1][j-1] - If characters don't match, we either delete from
s1(cost:f[i-1][j] + ord(s1[i-1])) or delete froms2(cost:f[i][j-1] + ord(s2[j-1])) - The base cases handle when one string is empty, requiring all characters from the other string to be deleted
The final answer is stored in f[m][n], representing the minimum cost to make both complete strings equal.
Intuition
The key insight is recognizing this as a variant of the classic Longest Common Subsequence (LCS) problem. When we want to make two strings equal by deletion, we're essentially finding what characters they can share in common (in the same order) and deleting everything else.
Think about it this way: if we find the longest common subsequence between the two strings, those are the characters we'll keep. Everything else needs to be deleted. But unlike the standard LCS where we just count characters, here we need to track the ASCII cost of deletions.
This naturally leads to a dynamic programming approach. At each position, we face a decision: if the current characters match, great - we don't need to delete anything and can build on our previous solution. If they don't match, we must delete at least one character, and we want to choose the option with minimum cost.
The state f[i][j] represents "what's the minimum cost to make the first i characters of s1 equal to the first j characters of s2?" This subproblem structure has optimal substructure - the solution to larger problems depends on solutions to smaller problems.
The base cases are intuitive: if one string is empty, we must delete all characters from the other string, so the cost is the sum of ASCII values of all those characters. As we build up the table, each cell depends only on previously computed cells (either f[i-1][j-1] when characters match, or the minimum of f[i-1][j] and f[i][j-1] when they don't), ensuring we can solve the problem bottom-up.
The beauty of this approach is that it systematically considers all possible ways to align the strings through deletion and automatically finds the one with minimum ASCII cost.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution implements a 2D dynamic programming approach. Let's walk through the implementation step by step:
1. Initialize the DP Table:
f = [[0] * (n + 1) for _ in range(m + 1)]
We create a 2D table f of size (m+1) × (n+1) where m = len(s1) and n = len(s2). The extra row and column handle the base cases where one string is empty.
2. Fill Base Cases - First Column:
for i in range(1, m + 1):
f[i][0] = f[i - 1][0] + ord(s1[i - 1])
When s2 is empty (column 0), we must delete all characters from s1 up to position i. Each f[i][0] accumulates the ASCII values of all characters from s1[0] to s1[i-1].
3. Fill Base Cases - First Row:
for j in range(1, n + 1):
f[0][j] = f[0][j - 1] + ord(s2[j - 1])
Similarly, when s1 is empty (row 0), we must delete all characters from s2 up to position j.
4. Fill the DP Table:
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i - 1] == s2[j - 1]:
f[i][j] = f[i - 1][j - 1]
else:
f[i][j] = min(
f[i - 1][j] + ord(s1[i - 1]),
f[i][j - 1] + ord(s2[j - 1])
)
For each cell f[i][j], we consider two cases:
-
Characters match (
s1[i-1] == s2[j-1]): No deletion needed. The cost remains the same asf[i-1][j-1], which represents the cost of making the previous substrings equal. -
Characters don't match: We must delete at least one character. We have two choices:
- Delete
s1[i-1]: Cost isf[i-1][j] + ord(s1[i-1]) - Delete
s2[j-1]: Cost isf[i][j-1] + ord(s2[j-1])
We choose the option with minimum cost.
- Delete
5. Return the Result:
return f[m][n]
The bottom-right cell f[m][n] contains the minimum ASCII sum needed to make the entire strings equal.
Time Complexity: O(m × n) - we fill each cell of the 2D table exactly once.
Space Complexity: O(m × n) - for storing the DP table. This could be optimized to O(min(m, n)) since we only need the previous row to compute the current row.
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 a small example with s1 = "sea" and s2 = "eat".
Step 1: Initialize the DP table We create a 4×4 table (since both strings have length 3, plus 1 for empty string base case).
Step 2: Fill base cases
- Row 0: If s1 is empty, delete all of s2:
f[0][1] = 101 ('e'),f[0][2] = 101 + 97 = 198 ('e'+'a'),f[0][3] = 198 + 116 = 314 ('e'+'a'+'t') - Column 0: If s2 is empty, delete all of s1:
f[1][0] = 115 ('s'),f[2][0] = 115 + 101 = 216 ('s'+'e'),f[3][0] = 216 + 97 = 313 ('s'+'e'+'a')
Step 3: Fill the DP table
Initial table with base cases:
"" e a t "" 0 101 198 314 s 115 ? ? ? e 216 ? ? ? a 313 ? ? ?
Computing each cell:
f[1][1]: 's' ≠ 'e', somin(f[0][1] + 115, f[1][0] + 101) = min(216, 216) = 216f[1][2]: 's' ≠ 'a', somin(f[0][2] + 115, f[1][1] + 97) = min(313, 313) = 313f[1][3]: 's' ≠ 't', somin(f[0][3] + 115, f[1][2] + 116) = min(429, 429) = 429f[2][1]: 'e' = 'e', sof[2][1] = f[1][0] = 115(no deletion needed)f[2][2]: 'e' ≠ 'a', somin(f[1][2] + 101, f[2][1] + 97) = min(414, 212) = 212f[2][3]: 'e' ≠ 't', somin(f[1][3] + 101, f[2][2] + 116) = min(530, 328) = 328f[3][1]: 'a' ≠ 'e', somin(f[2][1] + 97, f[3][0] + 101) = min(212, 414) = 212f[3][2]: 'a' = 'a', sof[3][2] = f[2][1] = 115(no deletion needed)f[3][3]: 'a' ≠ 't', somin(f[2][3] + 97, f[3][2] + 116) = min(425, 231) = 231
Final table:
"" e a t "" 0 101 198 314 s 115 216 313 429 e 216 115 212 328 a 313 212 115 231
Step 4: Result
f[3][3] = 231 is our answer. This represents deleting 's' from "sea" (ASCII 115) and 't' from "eat" (ASCII 116), leaving both strings as "ea". Total cost: 115 + 116 = 231.
Note that this is actually better than our initial guess of deleting 's' and 'a' (cost 212), as the DP algorithm found that keeping "ea" as the common subsequence minimizes the total deletion cost.
Solution Implementation
1class Solution:
2 def minimumDeleteSum(self, s1: str, s2: str) -> int:
3 """
4 Find the minimum ASCII sum of deleted characters to make two strings equal.
5
6 Args:
7 s1: First input string
8 s2: Second input string
9
10 Returns:
11 Minimum sum of ASCII values of deleted characters
12 """
13 # Get lengths of both strings
14 len_s1, len_s2 = len(s1), len(s2)
15
16 # Create DP table where dp[i][j] represents the minimum delete sum
17 # for s1[0:i] and s2[0:j] to make them equal
18 dp = [[0] * (len_s2 + 1) for _ in range(len_s1 + 1)]
19
20 # Initialize first column: delete all characters from s1[0:i]
21 for i in range(1, len_s1 + 1):
22 dp[i][0] = dp[i - 1][0] + ord(s1[i - 1])
23
24 # Initialize first row: delete all characters from s2[0:j]
25 for j in range(1, len_s2 + 1):
26 dp[0][j] = dp[0][j - 1] + ord(s2[j - 1])
27
28 # Fill the DP table
29 for i in range(1, len_s1 + 1):
30 for j in range(1, len_s2 + 1):
31 if s1[i - 1] == s2[j - 1]:
32 # Characters match: no deletion needed for these characters
33 dp[i][j] = dp[i - 1][j - 1]
34 else:
35 # Characters don't match: delete from either s1 or s2
36 # Choose the option with minimum ASCII sum
37 dp[i][j] = min(
38 dp[i - 1][j] + ord(s1[i - 1]), # Delete from s1
39 dp[i][j - 1] + ord(s2[j - 1]) # Delete from s2
40 )
41
42 # Return the minimum delete sum for entire strings
43 return dp[len_s1][len_s2]
441class Solution {
2 public int minimumDeleteSum(String s1, String s2) {
3 int m = s1.length();
4 int n = s2.length();
5
6 // dp[i][j] represents the minimum ASCII sum of deleted characters
7 // to make s1[0...i-1] and s2[0...j-1] equal
8 int[][] dp = new int[m + 1][n + 1];
9
10 // Initialize first column: delete all characters from s1[0...i-1]
11 for (int i = 1; i <= m; i++) {
12 dp[i][0] = dp[i - 1][0] + s1.charAt(i - 1);
13 }
14
15 // Initialize first row: delete all characters from s2[0...j-1]
16 for (int j = 1; j <= n; j++) {
17 dp[0][j] = dp[0][j - 1] + s2.charAt(j - 1);
18 }
19
20 // Fill the dp table
21 for (int i = 1; i <= m; i++) {
22 for (int j = 1; j <= n; j++) {
23 if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
24 // Characters match, no deletion needed
25 dp[i][j] = dp[i - 1][j - 1];
26 } else {
27 // Characters don't match, delete from either s1 or s2
28 // Choose the option with minimum ASCII sum
29 dp[i][j] = Math.min(
30 dp[i - 1][j] + s1.charAt(i - 1), // Delete from s1
31 dp[i][j - 1] + s2.charAt(j - 1) // Delete from s2
32 );
33 }
34 }
35 }
36
37 return dp[m][n];
38 }
39}
401class Solution {
2public:
3 int minimumDeleteSum(string s1, string s2) {
4 int m = s1.size();
5 int n = s2.size();
6
7 // dp[i][j] represents the minimum ASCII sum of deleted characters
8 // to make s1[0...i-1] and s2[0...j-1] equal
9 vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
10
11 // Base case: when s2 is empty, we need to delete all characters from s1
12 for (int i = 1; i <= m; ++i) {
13 dp[i][0] = dp[i - 1][0] + s1[i - 1];
14 }
15
16 // Base case: when s1 is empty, we need to delete all characters from s2
17 for (int j = 1; j <= n; ++j) {
18 dp[0][j] = dp[0][j - 1] + s2[j - 1];
19 }
20
21 // Fill the dp table
22 for (int i = 1; i <= m; ++i) {
23 for (int j = 1; j <= n; ++j) {
24 if (s1[i - 1] == s2[j - 1]) {
25 // If characters match, no deletion needed
26 dp[i][j] = dp[i - 1][j - 1];
27 } else {
28 // If characters don't match, delete from either s1 or s2
29 // Choose the option with minimum ASCII sum
30 dp[i][j] = min(dp[i - 1][j] + s1[i - 1], // Delete from s1
31 dp[i][j - 1] + s2[j - 1]); // Delete from s2
32 }
33 }
34 }
35
36 return dp[m][n];
37 }
38};
391/**
2 * Finds the minimum sum of ASCII values of characters that need to be deleted
3 * from both strings to make them equal
4 * @param s1 - First input string
5 * @param s2 - Second input string
6 * @returns The minimum ASCII sum of deleted characters
7 */
8function minimumDeleteSum(s1: string, s2: string): number {
9 const length1: number = s1.length;
10 const length2: number = s2.length;
11
12 // dp[i][j] represents the minimum ASCII sum of deleted characters
13 // to make s1[0...i-1] and s2[0...j-1] equal
14 const dp: number[][] = Array.from(
15 { length: length1 + 1 },
16 () => Array(length2 + 1).fill(0)
17 );
18
19 // Initialize first column: delete all characters from s1[0...i-1]
20 for (let i = 1; i <= length1; i++) {
21 dp[i][0] = dp[i - 1][0] + s1.charCodeAt(i - 1);
22 }
23
24 // Initialize first row: delete all characters from s2[0...j-1]
25 for (let j = 1; j <= length2; j++) {
26 dp[0][j] = dp[0][j - 1] + s2.charCodeAt(j - 1);
27 }
28
29 // Fill the dp table
30 for (let i = 1; i <= length1; i++) {
31 for (let j = 1; j <= length2; j++) {
32 if (s1[i - 1] === s2[j - 1]) {
33 // Characters match, no deletion needed
34 dp[i][j] = dp[i - 1][j - 1];
35 } else {
36 // Characters don't match, delete from either s1 or s2
37 // Choose the option with minimum ASCII sum
38 dp[i][j] = Math.min(
39 dp[i - 1][j] + s1.charCodeAt(i - 1), // Delete from s1
40 dp[i][j - 1] + s2.charCodeAt(j - 1) // Delete from s2
41 );
42 }
43 }
44 }
45
46 return dp[length1][length2];
47}
48Time and Space Complexity
Time Complexity: O(m * n) where m is the length of string s1 and n is the length of string s2.
The algorithm uses dynamic programming with a 2D table. The time complexity breaks down as follows:
- Initializing the first column takes
O(m)time - Initializing the first row takes
O(n)time - Filling the DP table requires nested loops that iterate through all
m * ncells, with each cell computation takingO(1)time - Therefore, the overall time complexity is
O(m) + O(n) + O(m * n) = O(m * n)
Space Complexity: O(m * n) where m is the length of string s1 and n is the length of string s2.
The space complexity is determined by:
- The 2D DP table
fof size(m + 1) × (n + 1)which stores the minimum ASCII sum of deleted characters for all subproblems - No additional data structures that scale with input size are used
- Therefore, the space complexity is
O((m + 1) * (n + 1)) = O(m * n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Index Mapping Between String and DP Table
One of the most common mistakes is confusion between DP table indices and string indices. The DP table uses 1-based indexing conceptually (where dp[i][j] represents the first i characters of s1 and first j characters of s2), while strings use 0-based indexing.
Incorrect Implementation:
# Wrong: Using i and j directly as string indices if s1[i] == s2[j]: # This will cause IndexError or wrong comparison dp[i][j] = dp[i - 1][j - 1]
Correct Implementation:
# Correct: Adjusting for 0-based string indexing if s1[i - 1] == s2[j - 1]: # i-1 and j-1 for string access dp[i][j] = dp[i - 1][j - 1]
2. Forgetting to Initialize Base Cases
Another pitfall is not properly initializing the first row and column of the DP table, or initializing them incorrectly.
Incorrect Implementation:
# Wrong: Forgetting to accumulate the ASCII values
for i in range(1, len_s1 + 1):
dp[i][0] = ord(s1[i - 1]) # Should be cumulative sum
Correct Implementation:
# Correct: Accumulating ASCII values for deletion
for i in range(1, len_s1 + 1):
dp[i][0] = dp[i - 1][0] + ord(s1[i - 1])
3. Misunderstanding the Problem Goal
Some might mistakenly try to find the longest common subsequence and delete everything else, which doesn't guarantee the minimum ASCII sum.
Why This Approach Fails:
Consider s1 = "ab" and s2 = "ba":
- LCS approach: Keep either "a" or "b", delete the other from both strings
- If we keep "a": Delete 'b' from both = 98 + 98 = 196
- If we keep "b": Delete 'a' from both = 97 + 97 = 194
- Optimal solution: Delete all characters = 97 + 98 + 98 + 97 = 390? No!
- Actually optimal: Keep empty string, delete all = 97 + 98 + 98 + 97 = 390
The correct approach is to minimize deletion cost, not maximize what we keep.
4. Space Optimization Pitfall
When optimizing space from O(m×n) to O(min(m,n)) using only two rows, a common mistake is not properly maintaining the previous row's values.
Incorrect Space-Optimized Version:
# Wrong: Overwriting values needed for next iteration prev = [0] * (len_s2 + 1) curr = [0] * (len_s2 + 1) # ... after filling curr row prev = curr # Wrong: This creates a reference, not a copy
Correct Space-Optimized Version:
# Correct: Properly alternating between two rows prev = [0] * (len_s2 + 1) curr = [0] * (len_s2 + 1) # ... after filling curr row prev, curr = curr, prev # Swap references # OR use: prev = curr[:] to create a copy
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
81public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
121class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85Recommended Readings
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
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!