【LeetCode 每日一题】712. 两个字符串的最小ASCII删除和——(解法一)记忆化搜索

Problem: 712. 两个字符串的最小ASCII删除和

文章目录

  • 整体思路
      • [1. 核心问题与转换](#1. 核心问题与转换)
      • [2. 算法与逻辑步骤](#2. 算法与逻辑步骤)
  • 完整代码
  • 时空复杂度
      • [1. 时间复杂度: O ( N × M ) O(N \times M) O(N×M)](#1. 时间复杂度: O ( N × M ) O(N \times M) O(N×M))
      • [2. 空间复杂度: O ( N × M ) O(N \times M) O(N×M)](#2. 空间复杂度: O ( N × M ) O(N \times M) O(N×M))

整体思路

1. 核心问题与转换

这段代码的核心目标是求出使两个字符串 s1s2 相等所需删除字符的最小 ASCII 值之和。

代码并没有直接去计算"删除"了什么,而是采用了一个巧妙的逆向思维 转换:
删除的最小 ASCII 和 = ( s1 和 s2 的总 ASCII 和 ) − ( s1 和 s2 最长公共子序列的 ASCII 和 ) \text{删除的最小 ASCII 和} = (\text{s1 和 s2 的总 ASCII 和}) - (\text{s1 和 s2 最长公共子序列的 ASCII 和}) 删除的最小 ASCII 和=(s1 和 s2 的总 ASCII 和)−(s1 和 s2 最长公共子序列的 ASCII 和)

  • 如果两个字符被保留(即它们属于公共子序列),那么它们不需要被删除。
  • 因为最终保留的字符串在 s1s2 中是相同的,所以对于公共子序列中的每一个字符 c,它在 s1 中贡献了 ASCII(c),在 s2 中也贡献了 ASCII(c)。因此,公共部分的权重是 2 * ASCII(c)

2. 算法与逻辑步骤

该解法使用了记忆化搜索(DFS + Memoization),即自顶向下的动态规划。

  • 第一步:计算总和

    • 计算 s1s2 所有字符的 ASCII 码总和 sum
  • 第二步:DFS 计算最长公共子序列(LCS)的最大权重

    • dfs(i, j) 定义为:计算 s1[0...i]s2[0...j] 之间公共子序列的最大 ASCII 和(注意:这里计算的是双倍和,即算上了两边的贡献)。
    • 状态转移
      1. 匹配 :如果 s1[i] == s2[j],说明这个字符可以作为公共子序列的一部分。递归结果为 dfs(i-1, j-1) + s1[i] * 2
      2. 不匹配 :如果字符不同,则必须舍弃 s1[i] 或者 s2[j] 中的一个。我们取两者中能产生更大公共和的路径:Math.max(dfs(i-1, j), dfs(i, j-1))
    • 边界条件 :当 i < 0j < 0,说明有一个字符串遍历完了,公共子序列和为 0。
  • 第三步:得出结果

    • 结果 = TotalSum - MaxCommonSum

完整代码

java 复制代码
import java.util.Arrays;

class Solution {
    // 将字符串转换为字符数组,方便通过索引访问
    private char[] s, t;
    // 记忆化数组,memo[i][j] 存储 s1[0...i] 和 s2[0...j] 的最大公共 ASCII 和
    private int[][] memo;

    // 深度优先搜索函数
    // i: s1 当前处理到的尾部索引
    // j: s2 当前处理到的尾部索引
    private int dfs(int i, int j) {
        // 递归终止条件(Base Case):
        // 如果任意一个字符串遍历结束(索引小于 0),则公共部分为 0
        if (i < 0 || j < 0) {
            return 0;
        }
        
        // 记忆化检查:
        // 如果当前状态 (i, j) 已经计算过(不等于初始值 -1),直接返回缓存的结果
        if (memo[i][j] != -1) {
            return memo[i][j];
        }

        // 核心状态转移逻辑:
        // 情况 1:当前两个字符相等,可以成为公共子序列的一部分
        if (s[i] == t[j]) {
            // 递归去处理前面的部分 (i-1, j-1)
            // 加上 s[i] * 2,因为这个字符在 s1 和 s2 中都保留了,
            // 而我们在初始计算 total sum 时是把 s1 和 s2 的所有字符都加起来的。
            // 要算出"删除的剩余部分",这里需要扣除两边的贡献。
            memo[i][j] = dfs(i - 1, j - 1) + s[i] * 2;
        } 
        // 情况 2:当前两个字符不相等
        else {
            // 只能舍弃 s[i] 或者 t[j] 中的一个,看哪种情况能保留更大的公共 ASCII 和
            // dfs(i - 1, j): 尝试跳过 s[i]
            // dfs(i, j - 1): 尝试跳过 t[j]
            memo[i][j] = Math.max(dfs(i - 1, j), dfs(i, j - 1));
        }
        
        // 返回计算结果
        return memo[i][j];
    }

    public int minimumDeleteSum(String s1, String s2) {
        // 1. 计算两个字符串所有字符的 ASCII 码总和
        // 使用 Java 8 Stream API 快速求和
        int sum = s1.chars().sum() + s2.chars().sum();
        
        // 2. 初始化数据结构
        s = s1.toCharArray();
        t = s2.toCharArray();
        int n = s.length;
        int m = t.length;
        
        // 初始化记忆化数组,大小为 n x m
        memo = new int[n][m];
        // 填充 -1 表示状态尚未被计算
        for (int[] row : memo) {
            Arrays.fill(row, -1);
        }
        
        // 3. 计算结果
        // 最小删除和 = 总和 - (两边都保留的公共子序列的最大 ASCII 和)
        // dfs(n - 1, m - 1) 返回的是 s1 和 s2 最大公共 ASCII 权重的两倍
        return sum - dfs(n - 1, m - 1);
    }
}

时空复杂度

1. 时间复杂度: O ( N × M ) O(N \times M) O(N×M)

  • 计算依据
    • 这是一个典型的二维动态规划问题。
    • 状态的数量由输入字符串的长度决定,共有 N × M N \times M N×M 个不同的状态 (i, j),其中 N N N 是 s1 的长度, M M M 是 s2 的长度。
    • 由于使用了记忆化数组 memo,每个状态最多只会被实际计算一次。
    • 每次计算内部只包含常数级别的比较和加法操作 O ( 1 ) O(1) O(1)。
  • 结论 : O ( N × M ) O(N \times M) O(N×M)。

2. 空间复杂度: O ( N × M ) O(N \times M) O(N×M)

  • 计算依据
    • 记忆化数组 :需要一个大小为 N × M N \times M N×M 的二维整数数组 memo 来存储中间结果。
    • 递归栈空间 :递归的最大深度为 N + M N + M N+M(最坏情况下,每次只减少一个索引)。但在 Big O 表示法中,通常数组的空间占主导地位。
    • 字符数组 st 占用 O ( N + M ) O(N + M) O(N+M) 空间。
  • 结论 : O ( N × M ) O(N \times M) O(N×M)。
相关推荐
知乎的哥廷根数学学派2 小时前
基于物理信息嵌入与多维度约束的深度学习地基承载力智能预测与可解释性评估算法(以模拟信号为例,Pytorch)
人工智能·pytorch·python·深度学习·算法·机器学习
古城小栈2 小时前
Rust 丰富&好用的 格式化语法
前端·算法·rust
AuroraWanderll2 小时前
类和对象(六)--友元、内部类与再次理解类和对象
c语言·数据结构·c++·算法·stl
leaves falling2 小时前
c语言-给定两个数,求这两个数的最大公约数
数据结构·算法
SamtecChina20232 小时前
Electronica现场演示 | 严苛环境下的56G互连
大数据·网络·人工智能·算法·计算机外设
Tim_102 小时前
【C++入门】05、复合类型-数组
开发语言·c++·算法
jikiecui2 小时前
信奥崔老师:三目运算 (Ternary Operator)
数据结构·c++·算法
无限进步_2 小时前
【C语言&数据结构】另一棵树的子树:递归思维的双重奏
c语言·开发语言·数据结构·c++·算法·github·visual studio