六、动规_两个数组的dp



两个数组的 dp

LCR 095. 最长公共子序列

给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

- 例如,`"ace"` 是 `"abcde"` 的子序列,但 `"aec"` 不是 `"abcde"` 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

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

示例 2:

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

示例 3:

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

思路

两个数组动态规划我们需要研究第一个数组 [0, i] 区间和第二个数组 [0, j] 区间,所以设dp[i][j]表示字符串text1的前i个字符(即 text1[0]text1[i - 1])和字符串text2的前j个字符(即 text2[0]text2[j - 1])的最长公共子序列的长度,

  • 如果text1 的第 i 个字符(即 text1[i - 1])和 text2 的第 j 个字符(即 text2[j - 1])相等,text1[i-1] == text2[j-1],则dp[i][j] = dp[i - 1][j - 1] + 1,表示当前字符相等,最长公共子序列长度在之前的基础上加 1。
  • 例如: text1 = "abc"text2 = "adc"。计算 dp[2][2] 时,即考虑 text1 的前两个字符 "ab"text2 的前两个字符 "ad",此时 text1[1] != text2[1]bd 不相等),所以 dp[2][2] = max(dp[1][2], dp[2][1])。计算 dp[3][3] 时,即考虑 text1 的前三个字符 "abc"text2 的前三个字符 "adc",此时 text1[2] == text2[2]cc 相等),之前 dp[2][2] 表示的是 "ab""ad" 的最长公共子序列长度,现在因为 text1 的第三个字符和 text2 的第三个字符相等,所以可以把这个相等的字符 'c' 加入到之前的最长公共子序列中,那么 dp[3][3] = dp[2][2] + 1
  • 如果text1 的第 i 个字符(即 text1[i - 1])和 text2 的第 j 个字符(即 text2[j - 1])不相等,text1[i - 1] != text2[j - 1],则dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]),表示当前字符不相等,不考虑 text1 的第 i 个字符或者不考虑 text2 的第 j 个字符。

代码

class Solution {
public:
    // 计算两个字符串 text1 和 text2 的最长公共子序列的长度
    int longestCommonSubsequence(string text1, string text2) {
        // 获取 text1 的长度
        int n = text1.size();
        // 获取 text2 的长度
        int m = text2.size();

        // 创建二维数组 dp 用于记录中间结果
        // dp[i][j] 表示 text1 的前 i 个字符和 text2 的前 j 个字符的最长公共子序列的长度
        // 初始值都设为 0,因为当 i 或 j 为 0 时(表示其中一个字符串为空),最长公共子序列长度为 0
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));

        // 遍历 text1 的每个字符
        for (int i = 1; i <= n; ++i) {
            // 遍历 text2 的每个字符
            for (int j = 1; j <= m; ++j) {
                // 如果 text1 的第 i 个字符(索引为 i - 1)和 text2 的第 j 个字符(索引为 j - 1)相等
                if (text1[i - 1] == text2[j - 1]) {
                    // 那么当前的最长公共子序列长度等于去掉这两个相等字符后子串的最长公共子序列长度加 1
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    // 如果当前两个字符不相等
                    // 则最长公共子序列长度为以下两种情况中的最大值:
                    // 1. 不考虑 text1 的第 i 个字符时的最长公共子序列长度,即 dp[i - 1][j]
                    // 2. 不考虑 text2 的第 j 个字符时的最长公共子序列长度,即 dp[i][j - 1]
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }

        // 返回 text1 的前 n 个字符和 text2 的前 m 个字符的最长公共子序列长度
        return dp[n][m];
    }
};

1035. 不相交的线

在两条独立的水平线上按给定的顺序写下 nums1nums2 中的整数。

现在,可以绘制一些连接两个数字 nums1[i]nums2[j] 的直线,这些直线需要同时满足:

-  `nums1[i] == nums2[j]`

- 且绘制的直线不与任何其他连线(非水平线)相交。

请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。

以这种方法绘制线条,并返回可以绘制的最大连线数。

示例 1:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

**输入:**nums1 = [1,4,2], nums2 = [1,2,4]
**输出:**2
**解释:**可以画出两条不交叉的线,如上图所示。 
但无法画出第三条不相交的直线,因为从 nums1[1]=4 到 nums2[2]=4 的直线将与从 nums1[2]=2 到 nums2[1]=2 的直线相交。

示例 2:

**输入:**nums1 = [2,5,1,2,5], nums2 = [10,5,2,1,5,2]
**输出:**3

示例 3:

**输入:**nums1 = [1,3,7,1,7,5], nums2 = [1,9,2,5,1]
**输出:**2

思路

本质和上面题目一样

代码

class Solution {
public:
    int maxUncrossedLines(vector<int>& text1, vector<int>& text2) {
        // 获取 text1 的长度
        int n = text1.size();
        // 获取 text2 的长度
        int m = text2.size();

        // 创建二维数组 dp 用于记录中间结果
        // dp[i][j] 表示 text1 的前 i 个字符和 text2 的前 j 个字符的最长公共子序列的长度
        // 初始值都设为 0,因为当 i 或 j 为 0 时(表示其中一个字符串为空),最长公共子序列长度为 0
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));

        // 遍历 text1 的每个字符
        for (int i = 1; i <= n; ++i) {
            // 遍历 text2 的每个字符
            for (int j = 1; j <= m; ++j) {
                // 如果 text1 的第 i 个字符(索引为 i - 1)和 text2 的第 j 个字符(索引为 j - 1)相等
                if (text1[i - 1] == text2[j - 1]) {
                    // 那么当前的最长公共子序列长度等于去掉这两个相等字符后子串的最长公共子序列长度加 1
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    // 如果当前两个字符不相等
                    // 则最长公共子序列长度为以下两种情况中的最大值:
                    // 1. 不考虑 text1 的第 i 个字符时的最长公共子序列长度,即 dp[i - 1][j]
                    // 2. 不考虑 text2 的第 j 个字符时的最长公共子序列长度,即 dp[i][j - 1]
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }

        // 返回 text1 的前 n 个字符和 text2 的前 m 个字符的最长公共子序列长度
        return dp[n][m];
    }
};

LCR 097. 不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE""ABCDE" 的一个子序列,而 "AEC" 不是)

题目数据保证答案符合 32 位带符号整数范围。

示例 1:

**输入:**s = "rabbbit", t = "rabbit"
**输出:**3
**解释:**
如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
**rabb**b**it**
**ra**b**bbit**
**rab**b**bit**

示例 2:

**输入:**s = "babgbag", t = "bag"
**输出:**5
**解释:**
如下图所示, 有 5 种可以从 s 中得到 "bag" 的方案。 
**ba**b**g**bag
**ba**bgba**g**
**b**abgb**ag**
ba**b**gb**ag**
babg**bag**

思路

dp[i][j] 表示 s 的前 i 个字符中包含 t 的前 j 个字符的子序列的个数。i 的取值范围是 0nns 的长度),j 的取值范围是 0mmt 的长度)

  • j = 0:此时 t 是一个空字符串。因为任何字符串都至少包含一个空字符串作为子序列(即不选取任何字符),所以对于任意的 i0 <= i <= m),都有 dp[i][0] = 1

  • i = 0j > 0 的情况:当 i = 0 时,字符串 s 为空串。而空串无法包含任何非空的子序列,所以对于任意的 j1 ≤ j ≤ m),都有 dp[0][j] = 0,当然这在初始化就完成了。

  • 如果 s 的第 i 个字符(索引为 i - 1)和 t 的第 j 个字符(索引为 j - 1)相等,也就是s[i-1] == t[j-1],可以用 s[i - 1] 来匹配 t[j - 1],需要看 s 的前 i - 1 个字符构成的子串中 t 的前 j - 1 个字符构成的子串出现的子序列数量,即 dp[i - 1][j - 1]

    • 例如:假设 s = "babgbag"t = "bag",我们来计算 dp[7][3]s 的第 7 个字符 'g'(即 s[6])和 t 的第 3 个字符 'g'(即 t[2])相等, s[6] == t[2],因为第 7 个 'g' 已经确定用来匹配 t 的第 3 个 'g' 了,那么我们就只需要看 s 的前 6 个字符 "babgba" 中包含 t 的前 2 个字符 "ba" 的子序列个数,也就是 dp[6][2] 中。
  • 不使用 s[i - 1] 这个字符:此时就相当于只考虑 s 的前 i - 1 个字符构成的子串中 t 的前 j 个字符构成的子串出现的子序列数量,即 dp[i - 1][j]

    • 例如:假设 s = "ABCDE"t = "ACE",我们要计算 dp[5][3],也就是 s 的前 5 个字符 "ABCDE" 中包含 t 的前 3 个字符 "ACE" 的子序列个数。考虑 s 的第 5 个字符 'E'(即 s[4])时,决定不使用这个 'E' 来参与构成 t 的子序列。那么此时,要计算 "ABCDE""ACE" 的子序列个数,其实和计算 "ABCD""ACE" 的子序列个数是一样的,因为 'E' 不参与,对结果没有影响。 "ABCD""ACE" 的子序列个数在之前计算 dp 数组时已经得到,存储在 dp[4][3] 中,虽然ABCD中不包含ACE,但是dp[4][3] 可以是0.
  • 如果当前字符不相等,退回到上面这种情况

unsigned long long 类型来避免整数溢出问题。

代码

class Solution {
public:
    // 计算字符串 s 的子序列中字符串 t 出现的个数
    int numDistinct(string s, string t) {
        // 获取字符串 s 的长度
        int n = s.size();
        // 获取字符串 t 的长度
        int m = t.size();

        // 创建二维数组 dp,dp[i][j] 表示 s 的前 i 个字符的子序列中 t 的前 j 个字符出现的个数
        vector<vector<unsigned long long>> dp(n + 1, vector<unsigned long long>(m + 1, 0));

        // 初始化:当 t 为空字符串时,s 的任意子串都包含空字符串,所以子序列个数为 1
        for (int i = 0; i <= n; ++i) {
            dp[i][0] = 1;
        }

        // 填充 dp 数组
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                // 如果 s 的第 i 个字符(索引为 i - 1)和 t 的第 j 个字符(索引为 j - 1)相等
                if (s[i - 1] == t[j - 1]) {
                    // 有两种情况:
                    // 1. 使用 s 的第 i 个字符来匹配 t 的第 j 个字符,此时个数为 dp[i - 1][j - 1]
                    // 2. 不使用 s 的第 i 个字符来匹配 t 的第 j 个字符,此时个数为 dp[i - 1][j]
                    // 总的个数为这两种情况之和
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                } else {
                    // 如果当前字符不相等,只能不使用 s 的第 i 个字符来匹配 t 的第 j 个字符
                    // 所以个数和 s 的前 i - 1 个字符的情况相同
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        // 返回最终结果,即 s 的前 n 个字符的子序列中 t 的前 m 个字符出现的个数
        return dp[n][m];
    }
};

44. 通配符匹配

给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?''*' 匹配规则的通配符匹配:

`'?'` 可以匹配任何单个字符。

`'*'` 可以匹配任意字符序列(包括空字符序列)。

判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

示例 1:

**输入:**s = "aa", p = "a"
**输出:**false
**解释:**"a" 无法匹配 "aa" 整个字符串。

示例 2:

**输入:**s = "aa", p = "*"
**输出:**true
**解释:**'*' 可以匹配任意字符串。

示例 3:

**输入:**s = "cb", p = "?a"
**输出:**false
**解释:**'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。

思路

  • dp[i][j] 表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否能匹配,其中 dp[i][j]true 表示可以匹配,false 表示不能匹配

  • dp[0][0]:当 sp 都为空字符串时,显然是匹配的,所以 dp[0][0] = true

  • dp[i][0]i > 0:当模式 s 不为空时,p 为空字符串,无法匹配,所以 dp[i][0] = false

  • dp[0][j]j > 0:当字符串 s 为空字符串时,只有当模式 p 全是 '*' 时才能匹配,因为 '*' 可以匹配空字符串。所以如果 p[j - 1]'*',则 dp[0][j] = dp[0][j - 1],否则当 p 中有一个不是'*',dp[ 0 ] [ j ] = false`。

  • p[j - 1] 是普通字符时s[i - 1] 等于 p[j - 1],那么 dp[i][j] 的值取决于 s 的前 i - 1 个字符和 p 的前 j - 1 个字符是否匹配,即 dp[i][j] = dp[i - 1][j - 1]

  • p[j - 1]'?''?' 可以匹配任何单个字符,所以 dp[i][j] 的值也取决于 s 的前 i - 1 个字符和 p 的前 j - 1 个字符是否匹配,即 dp[i][j] = dp[i - 1][j - 1]

  • p[j - 1]'\*'

    • '*' 匹配空字符串,此时 dp[i][j] = dp[i][j - 1]
    • '*' 匹配一个或多个字符,此时 dp[i][j] = dp[i - 1][j]
    • 两种情况都要只要有一个满足就行

代码

class Solution {
public:
    // 判断字符串 s 是否能和模式 p 匹配
    bool isMatch(string s, string p) {
        // 获取字符串 s 的长度
        int m = s.length();
        // 获取模式 p 的长度
        int n = p.length();

        // 创建二维布尔数组 dp,dp[i][j] 表示 s 的前 i 个字符和 p 的前 j 个字符是否匹配
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));

        // 初始化:当 s 和 p 都为空字符串时,它们是匹配的
        dp[0][0] = true;

        // 处理 s 为空字符串,p 不为空的情况
        for (int j = 1; j <= n; ++j) {
            // 如果 p 的第 j 个字符是 '*',则 dp[0][j] 的值取决于 dp[0][j - 1]
            if (p[j - 1] == '*') {
                dp[0][j] = dp[0][j - 1];
            }
        }

        // 填充 dp 数组
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                // 如果 p 的第 j 个字符和 s 的第 i 个字符相等,或者 p 的第 j 个字符是 '?'
                if (p[j - 1] == s[i - 1] || p[j - 1] == '?') {
                    // 则 dp[i][j] 的值取决于 dp[i - 1][j - 1]
                    dp[i][j] = dp[i - 1][j - 1];
                } 
                // 如果 p 的第 j 个字符是 '*'
                else if (p[j - 1] == '*') {
                    // 有两种情况:'*' 匹配空字符串或一个及以上字符
                    // dp[i][j - 1] 表示 '*' 匹配空字符串
                    // dp[i - 1][j] 表示 '*' 匹配一个及以上字符
                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
                }
            }
        }

        // 返回 s 的全部字符和 p 的全部字符是否匹配的结果
        return dp[m][n];
    }
};

10. 正则表达式匹配-待解决

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.''*' 的正则表达式匹配。

- `'.'` 匹配任意单个字符

- `'*'` 匹配零个或多个前面的那一个元素

所谓匹配,是要涵盖 **整个 **字符串 s 的,而不是部分字符串。

示例 1:

**输入:**s = "aa", p = "a"
**输出:**false
**解释:**"a" 无法匹配 "aa" 整个字符串。

示例 2:

**输入:**s = "aa", p = "a*"
**输出:**true
**解释:**因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:

**输入:**s = "ab", p = ".*"
**输出:**true
**解释:**".*" 表示可匹配零个或多个('*')任意字符('.')。

思路

1. 状态定义

dp[i][j] 表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否能匹配,dp[i][j]true 表示可以匹配,false 表示不能匹配。其中 i 取值范围是 0s.length()j 取值范围是 0p.length()

2. 初始化
  • dp[0][0]:当 sp 都为空字符串时,显然是匹配的,所以 dp[0][0] = true
  • dp[i][0]i > 0):当模式 p 为空字符串,而 s 不为空时,无法匹配,所以 dp[i][0] = false
  • dp[0][j]j > 0):当字符串 s 为空字符串时,只有当模式 p 形如 a*b*c* 这种形式(即偶数位置为 *)时才能匹配。因为 * 可以让其前面的字符出现零次,所以可以消除掉前面的字符。
3. 状态转移方程
  • p[j - 1] 不是 '*'

    • 如果 s[i - 1] 等于 p[j - 1] 或者 p[j - 1]'.',那么 dp[i][j] 的值取决于 s 的前 i - 1 个字符和 p 的前 j - 1 个字符是否匹配,即 dp[i][j] = dp[i - 1][j - 1]
    • 否则 dp[i][j] = false
  • p[j - 1]'*'

    • '\*' 让前面的字符出现零次:此时 dp[i][j] = dp[i][j - 2],因为可以把 p[j - 2]p[j - 1](即 x*)这部分直接忽略。
    • '\*' 让前面的字符出现至少一次:要求 s[i - 1] 等于 p[j - 2] 或者 p[j - 2]'.',并且 dp[i][j] = dp[i - 1][j],表示用 s 的前 i - 1 个字符和 p 的前 j 个字符匹配,然后 p[j - 2] 对应的字符再在 s 中多匹配一个 s[i - 1]
      综合这两种情况,dp[i][j] = dp[i][j - 2] || ( (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j] )
4. 最终结果

最终 dp[s.length()][p.length()] 表示字符串 s 和模式 p 是否能匹配。

代码

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.length();
        int n = p.length();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
        dp[0][0] = true;
        for (int j = 1; j <= n; j++) {
            if (p[j - 1] == '*' && dp[0][j - 2]) {
                dp[0][j] = true;
            }
        }
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (p[j - 1] != '*') {
                    dp[i][j] = (s[i - 1] == p[j - 1] || p[j - 1] == '.') && dp[i - 1][j - 1];
                } else {
                    dp[i][j] = dp[i][j - 2] || ( (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j] );
                }
            }
        }
        return dp[m][n];
    }
};

LCR 096. 交错字符串

给定三个字符串 s1s2s3,请判断 s3 能不能由 s1s2 交织(交错) 组成。

两个字符串 st 交织 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:

- `s = s1 + s2 + ... + sn`

- `t = t1 + t2 + ... + tm`

- `|n - m| &lt;= 1`

- 交织 是 `s1 + t1 + s2 + t2 + s3 + t3 + ...` 或者 `t1 + s1 + t2 + s2 + t3 + s3 + ...`

提示:a + b 意味着字符串 ab 连接。

示例 1:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

**输入:**s1 = "aabcc", s2 = "dbbca", s3 = "aadbbcbcac"
**输出:**true

示例 2:

**输入:**s1 = "aabcc", s2 = "dbbca", s3 = "aadbbbaccc"
**输出:**false

示例 3:

**输入:**s1 = "", s2 = "", s3 = ""
**输出:**true

思路

  • dp[i][j] 表示 s1 的前 i 个字符和 s2 的前 j 个字符是否能交错组成 s3 的前 i + j 个字符。其中 i 的取值范围是 0s1.length()j 的取值范围是 0s2.length()

  • dp[0][0]:当 s1s2 都为空字符串时,显然可以组成空字符串 s3,所以 dp[0][0] = true

  • dp[i][0]:当 s2 为空字符串时,s1 的前 i 个字符必须和 s3 的前 i 个字符完全相同,dp[i][0] 才为 true,即 dp[i][0] = dp[i - 1][0] && s1[i - 1] == s3[i - 1]

  • dp[0][j]:当 s1 为空字符串时,s2 的前 j 个字符必须和 s3 的前 j 个字符完全相同,dp[0][j] 才为 true,即 dp[0][j] = dp[0][j - 1] && s2[j - 1] == s3[j - 1]

  • 从s1中拿元素,

    • 假设 s1 = "abc"s2 = "def"s3 = "adbcef"。我们要判断 dp[2][1],也就是 s1 的前 2 个字符 “ab"s2的前 1 个字符"d"能否交错组成s3的前 3 个字符"adb”`。

      当我们考虑 s1 的第 2 个字符(索引为 i - 1 = 1),即 s1[1] = 'b's3 的第 i + j 个字符(索引为 i + j - 1 = 2),即 s3[2] = 'b',此时 s1[1] == s3[2]

      接下来,我们需要看 s1 的前 i - 1 = 1 个字符 "a"s2 的前 j = 1 个字符 "d" 能否交错组成 s3 的前 i + j - 1 = 2 个字符 "ad",这个情况已经在 dp[1][1] 中计算过了。如果 dp[1][1]true,那么就说明 s1 的前 2 个字符和 s2 的前 1 个字符能交错组成 s3 的前 3 个字符,即 dp[2][1]true

  • 从s2中拿元素,

    • dp[1][2],也就是 s1 的前 1 个字符 "a"s2 的前 2 个字符 "de" 能否交错组成 s3 的前 3 个字符 "adb"

      当我们考虑 s2 的第 2 个字符(索引为 j - 1 = 1),即 s2[1] = 'e's3 的第 i + j 个字符(索引为 i + j - 1 = 2),即 s3[2] = 'b',这里 s2[1] != s3[2],所以这种情况不满足。

      假设 s3 = "adebc",我们再看 dp[1][2],此时 s2[1] = 'e's3[2] = 'e',满足 s2[1] == s3[2]

      然后我们要检查 s1 的前 i = 1 个字符 "a"s2 的前 j - 1 = 1 个字符 "d" 能否交错组成 s3 的前 i + j - 1 = 2 个字符 "ad",这个情况存储在 dp[1][1] 中。如果 dp[1][1]true,那么 dp[1][2]true

代码

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) {
        int n = s1.length();
        int m = s2.length();
        int k = s3.length();

        // 如果 s1 和 s2 的长度之和不等于 s3 的长度,直接返回 false
        if (m + n != k) {
            return false;
        }

        // 创建二维布尔数组 dp 并初始化
        vector<vector<bool>> dp(n + 1, vector<bool>(m + 1, false));
        dp[0][0] = true;

        // 初始化第一列
        for (int i = 1; i <= n; ++i) {
            dp[i][0] = dp[i - 1][0] && s1[i - 1] == s3[i - 1];
        }

        // 初始化第一行
        for (int j = 1; j <= m; ++j) {
            dp[0][j] = dp[0][j - 1] && s2[j - 1] == s3[j - 1];
        }

        // 填充 dp 数组
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                if (s1[i - 1] == s3[i + j - 1] && dp[i - 1][j]) {
                    dp[i][j] = true;
                }
                if (s2[j - 1] == s3[i + j - 1] && dp[i][j - 1]) {
                    dp[i][j] = true;
                }
            }
        }

        return dp[n][m];
    }
};

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

给定两个字符串s1s2,返回 使两个字符串相等所需删除字符的 **ASCII **值的最小和 。

示例 1:

**输入:** s1 = "sea", s2 = "eat"
**输出:** 231
**解释:** 在 "sea" 中删除 "s" 并将 "s" 的值(115)加入总和。
在 "eat" 中删除 "t" 并将 116 加入总和。
结束时,两个字符串相等,115 + 116 = 231 就是符合条件的最小和。

示例 2:

**输入:** s1 = "delete", s2 = "leet"
**输出:** 403
**解释:** 在 "delete" 中删除 "dee" 字符串变成 "let",
将 100[d]+101[e]+101[e] 加入总和。在 "leet" 中删除 "e" 将 101[e] 加入总和。
结束时,两个字符串都等于 "let",结果即为 100+101+101+101 = 403 。
如果改为将两个字符串转换为 "lee" 或 "eet",我们会得到 433 或 417 的结果,比答案更大。

思路

正难则反:求两个字符串的最⼩ ASCII 删除和,其实就是找到两个字符串中所有的公共⼦序列⾥⾯, ASCII 最⼤和。

因此,我们的思路就是按照「最⻓公共⼦序列」的分析⽅式来分析。

static_cast<int> 显式地将一种数据类型转换为 int 数据类型

  • dp[i][j] 表示字符串 s1 的前 i 个字符和字符串 s2 的前 j 个字符的公共子序列的最大 ASCII 值之和

  • 如果 s1[i - 1] == s2[j - 1]:说明当前字符可以作为公共子序列的一部分,那么 dp[i][j] 就等于 s1 的前 i - 1 个字符和 s2 的前 j - 1 个字符的公共子序列的最大 ASCII 值之和,再加上当前相等字符的 ASCII 值,即 dp[i][j] = dp[i - 1][j - 1] + static_cast<int>(s1[i - 1])

  • 如果 s1[i - 1] != s2[j - 1]

    :此时需要比较两种情况:

    • 不考虑 s1 的第 i 个字符,即 dp[i][j] = dp[i - 1][j]
    • 不考虑 s2 的第 j 个字符,即 dp[i][j] = dp[i][j - 1]
      取这两种情况中的最大值,即 dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1])

totalSum 减去 dp[m][n] 的两倍(因为公共子序列的 ASCII 值在 totalSum 中被算了两次

代码

class Solution {
public:
    int minimumDeleteSum(string s1, string s2) {
        int n = s1.size();
        int m = s2.size();
        int sum = 0;
        for(int i=0; i<n; ++i){
            sum+=static_cast<int>(s1[i]);
        }
        for(int j=0; j<m; ++j){
            sum+=static_cast<int>(s2[j]);
        }
        vector<vector<int>>dp(n+1, vector<int>(m+1, 0));
        for(int i=1; i<=n; ++i){
            for(int j=1; j<=m; ++j){
                if(s1[i-1] == s2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+static_cast<int>(s1[i-1]);
                }else{
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return sum-2*dp[n][m];
    }
};

718. 最长重复子数组

给两个整数数组 nums1nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。

示例 1:

**输入:**nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
**输出:**3
**解释:**长度最长的公共子数组是 [3,2,1] 。

示例 2:

**输入:**nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
**输出:**5

思路

  • dp[i][j] 表示以 nums1的前i个数字 结尾和以 nums2的前j个数字 结尾的公共子数组的最长长度
  • 如果 nums1[i - 1] == nums2[j - 1],即 dp[i][j] = dp[i - 1][j - 1] + 1
    • 例如:假设 nums1 = [1, 2, 3, 2, 1]nums2 = [3, 2, 1, 4, 7],计算 dp[3][1] 时,以 nums1[2] 结尾的子数组是 [1, 2, 3],以 nums2[0] 结尾的子数组是 [3]。因为当前字符 3 相等,所以以 nums1[2] 结尾和以 nums2[0] 结尾的公共子数组的最长长度,就取决于以 nums1[1] 结尾和以 nums2[-1] (这里我们关注逻辑,实际代码不会有负数索引)结尾的公共子数组的最长长度。在 dp 数组中,就是 dp[i - 1][j - 1],也就是 dp[2][0],它的值为 0。那么 dp[3][1] 就等于 dp[2][0] + 1 = 0 + 1 = 1。,
  • 如果 nums1[i - 1] != nums2[j - 1],那么以 nums1[i - 1] 结尾和以 nums2[j - 1] 结尾的公共子数组的最长长度为 0,即 dp[i][j] = 0
  • 填充 dp 数组的过程中,记录下 dp[i][j] 的最大值,这个最大值就是两个数组中公共的、长度最长的子数组的长度

代码

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        int m = nums2.size();
        vector<vector<int>>dp(n+1, vector<int>(m+1, 0));
        int ret = 0;
        for(int i=1; i<=n; ++i){
            for(int j=1; j<=m; ++j){
                if(nums1[i-1] ==nums2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                    ret = max(ret, dp[i][j]);
                }else{
                    dp[i][j]=0;
                }
            }
        }
        return ret;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

penguin_bark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值