# \[1143]\[中等]\[动态规划] 最长公共子序列

## 题目描述

给定两个字符串 text1 和 text2，返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串：它是由原字符串在不改变字符的相对顺序的情况下删除某些字符（也可以不删除任何字符）后组成的新字符串。 例如，"ace" 是 "abcde" 的子序列，但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列，则返回 0。

示例 1:

```
输入：text1 = "abcde", text2 = "ace" 
输出：3  
解释：最长公共子序列是 "ace"，它的长度为 3。
```

示例 2:

```
输入：text1 = "abc", text2 = "abc"
输出：3
解释：最长公共子序列是 "abc"，它的长度为 3。
```

示例 3:

```
输入：text1 = "abc", text2 = "def"
输出：0
解释：两个字符串没有公共子序列，返回 0。
```

提示:

* 1 <= text1.length <= 1000
* 1 <= text2.length <= 1000
* 输入的字符串只含有小写英文字符。

## 解题思路

[动态规划之最长公共子序列（LCS）](https://leetcode-cn.com/problems/longest-common-subsequence/solution/dong-tai-gui-hua-zhi-zui-chang-gong-gong-zi-xu-lie/)

### 空间优化点

使用**滚动数组**的思想对使用的空间进行优化, 原本二维的状态转移表的空间复杂度为$$O(mn)$$, 其中$$m$$和$$n$$分别是两个字符串的长度. 观察状态转移方程, 对于$$(j, i)$$, 使用到的所有依赖为$$(j-1,i-1)$$, $$(j,i-1)$$, $$(j-1,i)$$. 所以我们仅存储:

* 上一列$$i-1$$的结果

这样, 从前到后(状态表从上到下)循环时, 在计算$$(j, i)$$, 已经计算得到了$$(j-1,i)$$, $$(j-1,i)$$覆盖了$$(j-1,i-1)$$. 这样我们就能计算得到$$(j, i)$$, 用来覆盖$$(j,i-1)$$, 从而在列的维度上逐步推进.

需要注意$$(j-1,i)$$覆盖了$$(j-1,i-1)$$的值, 而在计算$$(j, i)$$又需要$$(j-1,i-1)$$的值, 因此需要一个额外的标量变量来存储左上角(即$$(j-1,i-1)$$)的值.

代码如下:

```python
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        n, m = len(text1), len(text2)
        cache = [0] * n
        for i in range(m):
            for j in range(n):
                if j == 0:
                    la = cache[j]  # 记录左上角的值
                    cache[j] = 1 if text1[j] == text2[i] else cache[j]
                else:
                    tla = cache[j]
                    cache[j] = la + 1 if text1[j] == text2[i] else max(cache[j], cache[j - 1])
                    la = tla  # 记录左上角的值
        return cache[-1]
```

## 相关题目

* [\[718\]\[中等\]\[动态规划\]\[滑动窗口\] 最长重复子数组](https://blessbingo.gitbook.io/garnet/suan-fa/hua-dong-chuang-kou/718-zui-chang-zhong-fu-zi-shu-zu)
