Find the min and max number of substitutions
JavaScript (ES10), &nbsp;169 168&nbsp; 161 bytes

Saved 7 bytes thanks to @KevinCruijssen

Takes the strings as (s)(t). Returns [min, max].



s=&gt;t=&gt;[[...Array(d=(g=(k,m=s.length,n=m,c)=&gt;m*n?1+Math.min(g(k,m,n-1),g(k,--m,n),g(k-=c=s[m]!=t[--n],m,n)-!c):k?1/0:m-~n)())].flatMap((_,i)=&gt;g(i)-d?[]:j=i)[0],j]


Try it online! (fast test cases)

Try it online! (a longer test case which may time out if TIO is overloaded)

Commented

Helper function \$g\$

\$g\$ is a function that computes the standard Levenshtein distance when it's called with \$k\$ undefined, or the edit distance with exactly \$k\$ substitutions when it's called with \$k\ge0\$. An additional offset of \$+1\$ is added to the result in both cases.

NB: It is presented separately for readability but must be defined within the scope of the main function so that the source string \$s\$ and the target string \$t\$ are defined.

g = (                         // g is a recursive function taking:
  k,                          //   k = allowed number of substitutions, or undefined
  m = s.length,               //   m = 1st pointer, initialized to the length of s
  n = m,                      //   n = 2nd pointer, initialized to the length of s
  c                           //   c = substitution cost
) =&gt;                          //       (needs to be defined in the local scope)
  m * n ?                     // if both m and n are greater than 0:
    1 +                       //   add 1 to the final result
    Math.min(                 //   add the minimum of:
      g(k, m, n - 1),         //     recursive call for insertion with (m, n - 1)
      g(k, --m, n),           //     recursive call for deletion with (m - 1, n)
      g(                      //     recursive call for substitution:
        k -=                  //       set c if s[m - 1] is not equal to t[n - 1]
          c = s[m] != t[--n], //       and decrement k if c is set
        m, n                  //       use (m - 1, n - 1)
      ) - !c                  //     subtract 1 from the result if c is not set
    )                         //   end of Math.min()
  :                           // else (leaf node):
    k ?                       //   if k is not equal to 0 or NaN:
      1 / 0                   //     return +Infinity
                              //     (to make sure that this branch is not chosen)
    :                         //   else:
      m - ~n                  //     return m + n + 1


Main function

s =&gt; t =&gt;                     // s = source string, t = target string
  [                           // the final result is a pair:
    [                         //   build an array containing:
      ...Array(d = g()),      //     d entries, where d = Levenshtein distance (+1)
    ]                         //
    .flatMap((_, i) =&gt;        //   for each entry at position i in there:
      g(i)                    //     if the edit distance (+1) with i substitutions
      - d ?                   //     is not equal to d:
        []                    //       reject this entry
      :                       //     else:
        j = i                 //       append it to the array and copy i to j
    )[0],                     //   end of flatMap(); return the 1st entry (minimum)
    j                         //   followed by the last entry (maximum)
  ]                           // end of result