Skip to content

Commit e730371

Browse files
📝 更新动态规划文档,新增最长公共子串与最长公共子序列示例
- 在文档中新增了最长公共子串和最长公共子序列的详细描述与示例,展示了如何使用动态规划解决相关问题。 - 增加了动态规划的解题步骤,帮助读者理解算法的核心思想与实现过程。 - 优化了示例代码,提升了文档的实用性与参考价值。
1 parent b0d1568 commit e730371

File tree

1 file changed

+229
-1
lines changed

1 file changed

+229
-1
lines changed

docs/docs/选择编程语言/Python/99练习.mdx

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1754,7 +1754,7 @@ items = {
17541754
|音响| 0|0 | 0 | 0 | 3000|
17551755
|笔记本电脑| 0|0 | 0 | 2000 | 3000|
17561756
|吉他| 0|1500 | 1500 | 2000 | 3500 |
1757-
|iphone| 0|2000 | 3500 | 3500 | 4000 |
1757+
|iphone| 0|2000 | 3500 | 3500 | |
17581758
17591759
17601760
```bash showLineNumbers
@@ -1848,4 +1848,232 @@ for i in range(1, rows + 1):
18481848
# 最右下角就是最大价值
18491849
max_value = dp[rows][cols]
18501850
print("最大总价值:", max_value)
1851+
```
1852+
1853+
### 最长公共子串
1854+
1855+
当用户输入"hish"时,下面三个单词哪个更有可能是用户真正输入的单词?
1856+
1857+
- fish
1858+
- vista
1859+
- hsih(原单词的反转)
1860+
1861+
我们希望找到:哪个单词和 hish 之间有“最长的公共子串”(连续、顺序一致的公共部分),这个单词就更有可能是用户真正想输的。
1862+
1863+
#### 思路
1864+
1865+
这题是不能用集合取交集判断长度来解决的。
1866+
1867+
因为用户输入的单词是"hish",而单词"hsih"是原单词的反转。取集合交集判断虽然最长,但是交集不判断顺序。
1868+
1869+
最长公共子串要求顺序也一致的公共子字符串。
1870+
1871+
我们可以用动态规划来解决这个问题。
1872+
1873+
| 字符串 | h | i | s | h |
1874+
| --- | --- | --- | --- | --- |
1875+
| f | 0(左上角) | 0 | 0 | 0 |
1876+
| i | 0 | 0+1=1 | | |
1877+
| s | | | | |
1878+
| h | | | | |
1879+
1880+
如果两个字母不相同,则标记0 , 由于 f 和 `['h', 'i', 's', 'h']` 每个字母都不相同,所以标记0。
1881+
1882+
如果两个字母相同,值为右上角的邻居的值 + 1
1883+
1884+
<Highlight>为了让每个点都有一个左上角,你依然可以在第一行前和第一列前补0</Highlight>
1885+
1886+
直到填满所有表格。表格最大值为3,说明最多有3个连续相同的字符串。
1887+
1888+
| 字符串 | h | i | s | h |
1889+
| --- | --- | --- | --- | --- |
1890+
| f | 0 | 0 | 0 | 0 |
1891+
| i | 0 | 1| 0 | 0 |
1892+
| s | 0 | 0 | 2 | 0 |
1893+
| h | 0 | 0 | 0 | 3 |
1894+
1895+
接着我们把和hsih的表格也绘制出来。下表说明最多有1个连续相同的字符串。
1896+
1897+
| 字符串 | h | i | s | h |
1898+
| --- | --- | --- | --- | --- |
1899+
| h | 1 | 0 | 0 | 1 |
1900+
| s | 0 | 0| 1 | 0 |
1901+
| i | 0 | 1 | 0 | 0 |
1902+
| h | 1 | 0 | 0 | 1|
1903+
1904+
接着我们把和vista的表格也绘制出来。下表说明最多有2个连续相同的字符串。
1905+
1906+
| 字符串 | h | i | s | h |
1907+
| --- | --- | --- | --- | --- |
1908+
| v | 0 | 0 | 0 | 0 |
1909+
| i | 0 | 1| 0 | 0 |
1910+
| s | 0 | 0| 2 | 0 |
1911+
| t | 0 | 0 | 0 | 0 |
1912+
| a | 0 | 0 | 0 | 0 |
1913+
1914+
1915+
#### 题解
1916+
1917+
```python showLineNumbers
1918+
# 用户输入的单词
1919+
user_word = "hish"
1920+
1921+
# 备选单词列表
1922+
candidates = ["fish", "vista", "hsih"]
1923+
1924+
1925+
def longest_common_substring_length(a, b):
1926+
# 计算字符串 a 和 b 的最长公共子串长度(必须连续、顺序一致)
1927+
1928+
# 行:字符串 a 的字符个数,列:字符串 b 的字符个数
1929+
rows = len(a)
1930+
cols = len(b)
1931+
1932+
# dp[i][j] 表示:以 a 的第 i 个字符、b 的第 j 个字符结尾时
1933+
# 它们的「最长公共后缀子串」的长度
1934+
# 为了让每个点都有一个左上角,我们在前面额外补一行 0 和一列 0
1935+
dp = [[0] * (cols + 1) for _ in range(rows + 1)]
1936+
1937+
# 记录当前遇到的最长公共子串长度
1938+
longest = 0
1939+
1940+
# 从第 1 行、第 1 列开始填表(第 0 行和第 0 列是我们人为补上的 0)
1941+
for i in range(1, rows + 1):
1942+
for j in range(1, cols + 1):
1943+
# 如果两个字符相同,就把左上角的值 + 1
1944+
if a[i - 1] == b[j - 1]:
1945+
dp[i][j] = dp[i - 1][j - 1] + 1
1946+
# 顺便更新目前为止的最长公共子串长度
1947+
longest = max(longest, dp[i][j])
1948+
else:
1949+
# 如果两个字符不同,根据「最长公共子串」的定义,这个格子只能是 0
1950+
dp[i][j] = 0
1951+
1952+
return longest
1953+
1954+
1955+
# 用上面的函数分别计算每个候选单词和用户输入之间的最长公共子串长度
1956+
result = {}
1957+
for word in candidates:
1958+
# 每个候选单词和 user_word 的最长公共子串长度
1959+
length = longest_common_substring_length(word, user_word)
1960+
result[word] = length
1961+
1962+
# 找到最长公共子串长度最大的那个单词
1963+
best_word = max(result, key=result.get)
1964+
print("最有可能是用户真正输入的单词是:", best_word)
1965+
```
1966+
1967+
至此,你可以总结出动态规划的解题步骤:
1968+
1969+
- 确定维度?(在背包问题中是物品和背包空间 在最长公共子串问题中是原字符串和待比较字符串)
1970+
- 边界条件与初始值?(首行首列前补0)
1971+
- 状态转移方程是?(根据题目要求,确定状态转移方程,核心难点)
1972+
- 答案在哪?(背包问题中是右下角,最长公共子串问题中是表格最大值)
1973+
1974+
### 最长公共子序列
1975+
1976+
当用户输入"fosh"时,下面哪个更有可能是用户真正输入的单词?
1977+
1978+
- fort
1979+
- fish
1980+
1981+
#### 思路
1982+
1983+
按照之前最长公共子串的思路,两个单词的最长公共子串都为2。
1984+
1985+
我们可以绘制出两个单词的表格:
1986+
1987+
| 字符串 | f | o | s | h |
1988+
| --- | --- | --- | --- | --- |
1989+
| f | 1 | 0 | 0 | 0 |
1990+
| o | 0 | 2| 0 | 0 |
1991+
| r | 0 | 0| 0 | 0 |
1992+
| t | 0 | 0 | 0 | 0 |
1993+
1994+
| 字符串 | f | o | s | h |
1995+
| --- | --- | --- | --- | --- |
1996+
| f | 1 | 0 | 0 | 0 |
1997+
| i | 0 | 0| 0 | 0 |
1998+
| s | 0 | 0| 1 | 0 |
1999+
| h | 0 | 0 | 0 | 2|
2000+
2001+
但是fish和fosh的相似度更高,怎么修改状态转移方程,使得fish和fosh的相似度更高?
2002+
2003+
把状态转移方程改为:
2004+
2005+
- 如果两个字母不相同,则<HoverText text="标记从左上角、上方和左方的值中取最大值" explanation="最长公共子串是取0"/>
2006+
2007+
- 如果两个字母相同,值为右上角的邻居的值 + 1
2008+
2009+
| 字符串 | - | f | o | s | h |
2010+
| --- | --- | --- | --- | --- | --- |
2011+
| - | 0 | 0 | 0 | 0 | 0 |
2012+
| f | 0 | 1 | 1 | 1 | 1 |
2013+
| o | 0 | 1| 2 | 2 | 2 |
2014+
| r | 0 | 1 | 2 | 2 | 2 |
2015+
| t | 0 | 1 | 2 | 2 | 2 |
2016+
2017+
表格最大值为2,说明最多有2个连续相同的字符串。
2018+
2019+
| 字符串 | - | f | o | s | h |
2020+
| --- | --- | --- | --- | --- | --- |
2021+
| - | 0 | 0 | 0 | 0 | 0 |
2022+
| f | 0 | 1 | 1 | 1 | 1 |
2023+
| i | 0 | 1| 1 | 1 | 1 |
2024+
| s | 0 | 1 | 1 | 2 | 2 |
2025+
| h | 0 | 1 | 2 | 2 | 3 |
2026+
2027+
表格最大值为3,说明最多有3个连续相同的字符串。
2028+
2029+
#### 题解
2030+
2031+
```python showLineNumbers
2032+
# 用户输入的单词
2033+
user_word = "fosh"
2034+
2035+
# 备选单词列表
2036+
candidates = ["fort", "fish"]
2037+
2038+
2039+
def longest_common_subsequence_length(a, b):
2040+
# 计算字符串 a 和 b 的最长公共子序列长度(字符顺序一致,但不要求连续)
2041+
2042+
# 行:字符串 a 的字符个数,列:字符串 b 的字符个数
2043+
rows = len(a)
2044+
cols = len(b)
2045+
2046+
# dp[i][j] 表示:a 的前 i 个字符、b 的前 j 个字符之间
2047+
# 能得到的「最长公共子序列」的长度
2048+
# 为了让每个点都有一个左上角、上方和左方,我们在前面额外补一行 0 和一列 0
2049+
dp = [[0] * (cols + 1) for _ in range(rows + 1)]
2050+
2051+
# 从第 1 行、第 1 列开始填表(第 0 行和第 0 列是我们人为补上的 0)
2052+
for i in range(1, rows + 1):
2053+
for j in range(1, cols + 1):
2054+
if a[i - 1] == b[j - 1]:
2055+
# 如果两个字符相同,值为左上角的邻居的值 + 1
2056+
dp[i][j] = dp[i - 1][j - 1] + 1
2057+
else:
2058+
# 如果两个字符不同,则从「左上角、上方和左方」三者中取最大值
2059+
dp[i][j] = max(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1])
2060+
2061+
# 整个表格的右下角,就是最长公共子序列的长度
2062+
return dp[rows][cols]
2063+
2064+
2065+
# 用上面的函数分别计算每个候选单词和用户输入之间的最长公共子序列长度
2066+
result = {}
2067+
for word in candidates:
2068+
# 每个候选单词和 user_word 的最长公共子序列长度
2069+
length = longest_common_subsequence_length(word, user_word)
2070+
result[word] = length
2071+
2072+
# 打印每个候选单词的最长公共子序列长度,方便对照
2073+
for word, length in result.items():
2074+
print(f"{word} 和 {user_word} 的最长公共子序列长度为: {length}")
2075+
2076+
# 找到最长公共子序列长度最大的那个单词
2077+
best_word = max(result, key=result.get)
2078+
print("最有可能是用户真正输入的单词是:", best_word)
18512079
```

0 commit comments

Comments
 (0)