PROJECTENGRAM

[PS] LOVERS ARCANA

Lovers Arcana: Edit Distance & Subset Sum

Tags: #◇CSALGCM #◇Problem Sets #◇HackerRank #◇Algorithms #◇Dynamic Programming


◇Overview

The Lovers Arcana covers structural alignment and combinatorial decision problems: Edit Distance (Levenshtein Distance) and the Subset Sum / Partition Problem.


◇Problem 1: Edit Distance (Levenshtein Distance)

Problem Description

Given two strings AA of length mm and BB of length nn, determine the minimum number of single-character edit operations required to convert string AA into string BB. The allowed operations are:

  1. Insert a character.
  2. Delete a character.
  3. Replace a character.

Constraints:

  • 0≤m,n≤10000 \le m, n \le 1000
  • Strings consist of lowercase English letters.

Approach & Intuition

Let E[i][j]E[i][j] denote the minimum edit distance between prefixes A[1…i]A[1 \dots i] and B[1…j]B[1 \dots j].

  • Base cases:
    • E[i][0]=iE[i][0] = i: Converting A[1…i]A[1 \dots i] to empty string requires ii deletions.
    • E[0][j]=jE[0][j] = j: Converting empty string to B[1…j]B[1 \dots j] requires jj insertions.
  • Inductive Step:
    • If A[i]=B[j]A[i] = B[j]: No new operation needed; E[i][j]=E[i−1][j−1]E[i][j] = E[i-1][j-1].
    • If A[i]≠B[j]A[i] \ne B[j]: Choose the minimum among:
  • Delete A[i]A[i]: E[i−1][j]+1E[i-1][j] + 1
  • Insert B[j]B[j]: E[i][j−1]+1E[i][j-1] + 1
  • Replace A[i]A[i] with B[j]B[j]: E[i−1][j−1]+1E[i-1][j-1] + 1

Mathematical Recurrence

E[i][j]={jif i=0iif j=0E[i−1][j−1]if A[i]=B[j]1+min⁡(E[i−1][j],E[i][j−1],E[i−1][j−1])if A[i]≠B[j]E[i][j] = \begin{cases} j & \text{if } i = 0 \\ i & \text{if } j = 0 \\ E[i-1][j-1] & \text{if } A[i] = B[j] \\ 1 + \min(E[i-1][j], E[i][j-1], E[i-1][j-1]) & \text{if } A[i] \ne B[j] \end{cases}

Stepped Calculation Trace

Let A="horse"A = \text{"horse"} (m=5m=5) and B="ros"B = \text{"ros"} (n=3n=3):

Tabulation Matrix: E[i][j]E[i][j]

A\BA \backslash B∅\emptysetros
∅\emptyset0123
h1123
o2212
r3222
s4332
e5443

Optimal Operation Traceback:

  • (5,3)(5, 3) ['e' ≠\ne 's']: Minimum neighbor is (4,3)=2  ⟹  (4, 3) = 2 \implies Delete 'e' (Cost +1).
  • (4,3)(4, 3) ['s' = 's']: Diagonal (3,2)=2  ⟹  (3, 2) = 2 \implies Match! (Cost +0).
  • (3,2)(3, 2) ['r' ≠\ne 'o']: Minimum neighbor is (2,2)=1  ⟹  (2, 2) = 1 \implies Delete 'r' (Cost +1).
  • (2,2)(2, 2) ['o' = 'o']: Diagonal (1,1)=1  ⟹  (1, 1) = 1 \implies Match! (Cost +0).
  • (1,1)(1, 1) ['h' ≠\ne 'r']: Diagonal (0,0)=0  ⟹  (0, 0) = 0 \implies Replace 'h' with 'r' (Cost +1).
  • Total Operations: 3 (Replace 'h' →\to 'r', Delete 'r', Delete 'e').

◇Problem 2: The Subset Sum Problem

Problem Description

Given a multiset of non-negative integers S={s1,s2,…,sn}S = \{s_1, s_2, \dots, s_n\} and a target integer sum TT. Determine whether there exists a non-empty subset of SS whose elements sum up to exactly TT.

Constraints:

  • 1≤n≤1001 \le n \le 100
  • 1≤si≤10001 \le s_i \le 1000
  • 1≤T≤1041 \le T \le 10^4

Approach & Intuition: Boolean State Space

Let DP[i][t]DP[i][t] be a boolean flag indicating whether a sum of tt can be formed using a subset of the first ii elements.
For element sis_i:
DP[i][t]=DP[i−1][t]∨(t≥si∧DP[i−1][t−si])DP[i][t] = DP[i-1][t] \lor (t \ge s_i \land DP[i-1][t - s_i])
Base case: DP[i][0]=trueDP[i][0] = \text{true} (the empty subset achieves sum 0). All other DP[0][t]=falseDP[0][t] = \text{false}.

Stepped Calculation Trace

Let S=[3,4,5,2]S = [3, 4, 5, 2] and T=7T = 7:

  • Init: DP=[T,F,F,F,F,F,F,F]DP = [T, F, F, F, F, F, F, F]
  • Include s1=3s_1 = 3: Sums formed: {0,3}\{0, 3\}
  • Include s2=4s_2 = 4: Sums formed: {0,3,4,7}\{0, 3, 4, 7\}   ⟹  DP[7]=true\implies DP[7] = \text{true} immediately!
  • The subset is {3,4}\{3, 4\}, yielding 3+4=73 + 4 = 7.

◇Complete C++ Implementation

『』
CPP
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

// Problem 1: Edit Distance
int minEditDistance(const string& A, const string& B) {
    int m = A.size();
    int n = B.size();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));

    for (int i = 0; i <= m; i++) dp[i][0] = i;
    for (int j = 0; j <= n; j++) dp[0][j] = j;

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (A[i - 1] == B[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = 1 + min({dp[i - 1][j],     // Deletion
                                    dp[i][j - 1],     // Insertion
                                    dp[i - 1][j - 1]  // Substitution
                                   });
            }
        }
    }
    return dp[m][n];
}

// Problem 2: 1D Space-Optimized Subset Sum
bool canPartitionSubsetSum(const vector<int>& S, int target) {
    vector<bool> dp(target + 1, false);
    dp[0] = true; // Base case: Sum 0 is always achievable

    for (int num : S) {
        // Traverse backwards to use each number at most once
        for (int t = target; t >= num; t--) {
            if (dp[t - num]) {
                dp[t] = true;
            }
        }
    }
    return dp[target];
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    // Test Edit Distance
    string word1 = "horse";
    string word2 = "ros";
    cout << "Min Edit Distance ('horse' -> 'ros'): " 
         << minEditDistance(word1, word2) << "\n"; // Output: 3

    // Test Subset Sum
    vector<int> nums = {3, 34, 4, 12, 5, 2};
    int target = 9;
    cout << "Subset sum for target 9 exists: " 
         << (canPartitionSubsetSum(nums, target) ? "Yes" : "No") << "\n"; // Output: Yes (4 + 5)

    return 0;
}

◇References & Navigation