Solution 1.1: Brute force
  
- Consider all possible sequences of one string
- For each of these sequences, check if it's a subsequence in the other string
- If it is, note its length if it's longer than the last longest subsequence
 
def longest_common_subsequence(s1, s2)
  m, n = s1.length, s2.length
  longest_length = 0
  (1 .. 2**m-1).each do |bitmask|
    subsequence = []
    i = 0
    while 2**i <= bitmask
      if bitmask & 2**i != 0
        subsequence << s1[i]
      end
      i += 1
    end
    i = 0
    s2.each_char do |c|
      if subsequence[i] == c
        i += 1
      end
    end
    if i == subsequence.length && i > longest_length
      longest_length = i
    end
  end
  longest_length
end
    
      | Time | O(n * 2m) | m comparisons for each of 2n possible subsequences | 
    
      | Space | O(1) | The length of the longest subsequence seen | 
 
Solution 1.2: Dynamic programming
  
- Compare every prefix of one string to the other
- If the last characters of each prefix match, add 1 to the longest length
without the last characters
- Otherwise, the longest length is the last longest length
 
def longest_common_subsequence(s1, s2)
  m, n = s1.length, s2.length
  lcs_lengths = Array.new(m + 1) { Array.new(n + 1, 0) }
  (1 .. m).each do |i|
    (1 .. n).each do |j|
      if s1[i-1] == s2[j-1]
        lcs_lengths[i][j] = 1 + lcs_lengths[i-1][j-1]
      else
        lcs_lengths[i][j] = [
          lcs_lengths[i-1][j],
          lcs_lengths[i][j-1]
        ].max
      end
    end
  end
  lcs_lengths[m][n]
end
    
      | Time | O(mn) | Two loops of m and n time | 
    
      | Space | O(mn) | 2D array of longest common subsequence lengths |