583. 两个字符串的删除操作

难度:中等 | 主题:DP——最长公共子序列

题目

给定两个单词 word1 和 word2,每一步可以删除任意一个字符串中的一个字符。返回使两个单词相同所需的最小步数。

示例

输入:word1 = “sea”, word2 = “eat”

输出:2

解释:删除 “sea” 中的 ‘s’(→“ea”),删除 “eat” 中的 ‘t’(→“ea”)


思路

先讲个故事:两段纸条

你手上有两段英文句子打印在纸条上,你想让它们变得一模一样——但规则残酷:只能剪掉字符,不能添加或替换。

你很快意识到:最少剪掉的字符 = 两段总长 - 2 × 最长共同片段

就像两条 DNA 序列比对——保留最长的匹配段,删掉两端不匹配的部分。


引导式推导:从暴力到 LCS

第 1 步:暴力直觉

尝试各种删除组合,看能匹配成什么。指数级,n=10 就受不了。

第 2 步:转换视角

删除后两个字符串相同 → 它们变成了一个公共子序列。删除字符最少 → 这个公共子序列最长

 
minDelete = len(word1) + len(word2) - 2 × LCS(word1, word2)
 

LCS 就是 1143最长公共子序列。


graph LR

    A["word1: sea"] -->|删除 s| A1["ea"]

    B["word2: eat"] -->|删除 t| B1["ea"]

    A1 --> C["公共结果: ea"]

    B1 --> C

    D["LCS(word1,word2) = ea (长度2)"] -.-> C

第 3 步:LCS 的 DP 推导

定义 dp[i][j] = word1 前 i 个字符和 word2 前 j 个字符的 LCS 长度。

 
if word1[i-1] == word2[j-1]:
 
    dp[i][j] = dp[i-1][j-1] + 1    # 字符匹配,LCS 长度 +1
 
else:
 
    dp[i][j] = max(dp[i-1][j],      # 跳过 word1[i-1]
 
                   dp[i][j-1])      # 跳过 word2[j-1]
 

也可以直接 DP:删除操作

不求 LCS,直接算最少删除次数:

初始化:一个空串要删掉另一个串的所有字符 → dp[i][0] = i, dp[0][j] = j

 
if word1[i-1] == word2[j-1]:
 
    dp[i][j] = dp[i-1][j-1]          # 相同,不用删
 
else:
 
    dp[i][j] = 1 + min(dp[i-1][j],   # 删 word1[i-1]
 
                       dp[i][j-1])   # 删 word2[j-1]
 

两种方法对比


graph TB

    subgraph 方法一: LCS转换

        A1["求 LCS"] --> A2["删除数 = 总长 - 2×LCS"]

    end

    subgraph 方法二: 直接 DP

        B1["dp[i][j] = 最少删除数"] --> B2["相同 → 不删<br/>不同 → 删一个"]

    end

方法思路代码量
LCS 转换先求 LCS,再换算两段逻辑
直接 DP一步到位,直接算删除一段逻辑

两者复杂度一样,LCS 转换更易懂,直接 DP 更紧凑。


代码

LCS 转换法:

 
def minDistance(word1, word2):
 
    m, n = len(word1), len(word2)
 
    dp = [[0] * (n + 1) for _ in range(m + 1)]
 
    for i in range(1, m + 1):
 
        for j in range(1, n + 1):
 
            if word1[i-1] == word2[j-1]:
 
                dp[i][j] = dp[i-1][j-1] + 1
 
            else:
 
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
 
    lcs = dp[m][n]
 
    return m + n - 2 * lcs
 

直接 DP 法:

 
def minDistance(word1, word2):
 
    m, n = len(word1), len(word2)
 
    dp = [[0] * (n + 1) for _ in range(m + 1)]
 
    for i in range(m + 1):
 
        dp[i][0] = i
 
    for j in range(n + 1):
 
        dp[0][j] = j
 
    for i in range(1, m + 1):
 
        for j in range(1, n + 1):
 
            if word1[i-1] == word2[j-1]:
 
                dp[i][j] = dp[i-1][j-1]
 
            else:
 
                dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1])
 
    return dp[m][n]
 

复杂度

指标解释
时间O(m × n)双循环填表
空间O(m × n)二维 DP(可优化到 O(n))

实战考量

频率分析

出现在:美团/字节/阿里 二三面,常作为 72编辑距离 的前置题。先让你做 583(只有删除),再追问”如果可以增/改怎么办”引出 72。

延伸思考

Q:为什么想到转 LCS?

A:删除操作的本质是保留公共部分。删除最少 → 保留最长公共子序列。这是”逆向思维”——从”删什么”变成”留什么”。

Q:直接 DP 的初始化为什么是 dp[i][0] = i?

A:word2 为空时,要把 word1 的前 i 个字符全删掉才能变成空串。所以需要 i 步。

Q:空间能优化吗?

A:可以。dp[i][j] 只依赖 dp[i-1][j-1]dp[i-1][j]dp[i][j-1]。用一维数组 + 左上角变量记录,降到 O(n)。

Q:如果还能增加和替换字符(72 题)?

A:操作集合从 1 种(删除)扩展到 3 种(增/删/改)。转移方程变成 dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + (0 if 相同 else 1))

易错点

  • 初始化 dp[i][0] = idp[0][j] = j 不能忘

  • LCS 法最后要 总长 - 2×LCS,不是 总长 - LCS

  • 直接 DP 中字符相同时是 = dp[i-1][j-1],不是 = dp[i-1][j-1] + 1(因为不删,不是加)


生活类比

两段纸条 → LCS → 删除数

两段文字要变一样,就像两个乐高模型要变成同一个形状——你只能拆零件。

最省事的做法:保留最长公共骨架,拆掉骨架外的部分。

总零件数 - 2 × 公共骨架数 = 最少拆除数。


相关题目

题目关系
1143最长公共子序列LCS 基础,本题核心子问题
72编辑距离进阶版,可增/删/改三种操作

→ 返回题单:LeetCode学习路线图 > 十三、动态规划

速记卡(面试闪卡)

Q1:一句话讲清「583. 两个字符串的删除操作」到底是什么?

A:两个单词只能删字符,求让它们变得相同所需的最少删除步数。

Q2:题目核心 —— 怎么理解?

A:像两段纸条只能剪不能贴,要剪到一模一样;英文 Delete Operation for Two Strings,最少删 = 总长 - 2×最长公共子序列 LCS。

Q3:思路拆解 —— 怎么理解?

A:如同两条 DNA 比对:保留最长公共骨架,拆掉两端不匹配的;英文 Longest Common Subsequence(LCS),逆向思维”留什么”而非”删什么”。

Q4:代码骨架 —— 怎么理解?

A:好比填表:dp[i][0]=i 表示空串要删光对方;字符相同不删,不同则删一边取较小;LCS 法最后用 总长-2×LCS。

Q5:复杂度与实战 —— 怎么理解?

A:如同双循环填二维表,时间 O(m×n)、空间 O(m×n);英文 Edit Distance 前置题,先考删除再追问增改。

Q6:核心速记主线有哪些?

  • 转化:删最少 = 留最长公共子序列 LCS

  • 直接 DP:dp[i][0]=i 初始化,相同不删不同删一

  • LCS 法:先求 LCS 再 总长-2×LCS

  • 复杂度:时间 O(m×n),空间 O(m×n) 可压到 O(n)

口诀

A:两串删除变相同

留最长公共骨

填表相同不必删

时间O(mn)稳拿分

相关链接