【动态规划六】两个数组的dp问题

目录

leetcode题目

一、最长公共子序列

二、不相交的线

三、不同的子序列

四、通配符匹配

五、正则表达式匹配

六、交错字符串

七、两个字符串的最小ASCII删除和

八、最长重复子数组


leetcode题目

一、最长公共子序列

1143. 最长公共子序列 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/longest-common-subsequence/1.题目解析

找两个字符串的最长公共子序列的长度

2.算法分析

1.状态表示

经验+题目要求: 选取第一个字符串[0, i] 区间以及第二个字符串[0, j] 区间作为研究对象

dp[i][j]: s1的[0, i]区间以及s2的[0, j]区间所有的子序列中,最长的公共子序列的长度

2.状态转移方程

根据最后一个位置的情况,分情况讨论

3.初始化

在原始dp表上添加一行一列,  第一行表示第一个字符串为空,第二行表示第二个字符串为空,根据实际含义,将第一行第一列都填成0,填表就不会越界了并且后续填表是正确的,而我们可以在s1和s2前面都加'_', 这样dp表和原始字符串的下标就一一对应了

4.填表顺序

从上往下填表,每一行从左往右

5.返回值

dp[m][n]

3.算法代码

class Solution {
public:
    int longestCommonSubsequence(string s1, string s2) 
    {
        int m = s1.size(), n = s2.size();
        //1.创建dp表 + 初始化
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        s1 = " " + s1, s2 = " " + s2; 
        //2.填表
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(s1[i] == s2[j])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
            }
        }
        //3.返回值
        return dp[m][n];
    }
};

二、不相交的线

1035. 不相交的线 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/uncrossed-lines/1.题目解析

给定两个数组,要求将两个数组中相同的元素进行连线,但是不能相交,并且一个数字只能属于一个线, 求最多的线段个数

2.算法分析
本质和题目一是一样的,只是题目一是在两字符串中求最长公共子序列,而本题是在两个数组中秋最长公共子序列
解法和题目一是一模一样的~
只是本题直接在数组前加空格不太方便,可以用修改下标映射关系,让其一一对应即可
3.算法代码

class Solution 
{
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2)
    {
        int m = nums1.size(), n = nums2.size();
        //1.创建dp表 + 初始化
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        //2.填表
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(nums1[i-1] == nums2[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
            }
        }
        //3.返回值
        return dp[m][n];
    }
};

三、不同的子序列

115. 不同的子序列 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/distinct-subsequences/

1.题目解析

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数

2.算法分析

1.状态表示

dp[i][j]: s字符串的[0, j]区间内的所有子序列中,有多少个t字符串[0, i]区间内的子串

2.状态转移方程

3.初始化

dp表添加一行一列, t字符串和s字符串前加 "_"

第一行i位0, 表示t是空串,这种情况下无论s是啥样子,只要一个不选,就是空串,因此第一行全部初始化成1

第一列i为0, 表示s是空串,这种情况下只有当t是空串(dp[0][0]),才是1种情况,其余都是0

4.填表顺序

从上往下填表,每一行从左往右

5.返回值

dp[m][n]

3.算法代码

class Solution {
public:
    int numDistinct(string s, string t) 
    {
        int m = t.size(), n = s.size();
        s = " " + s, t = " " + t;
        //1.创建dp表
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        //2.初始化
        for(int j = 0; j <= n; j++) 
            dp[0][j] = 1;
        //3.填表
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                dp[i][j] += (dp[i][j-1] % (1000000000 + 7));
                if(t[i] == s[j])
                    dp[i][j] += (dp[i-1][j-1] % (1000000000 + 7));
            }
        }
        //4.返回值
        return dp[m][n] % (1000000000 + 7);
    }
};

四、通配符匹配

44. 通配符匹配 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/wildcard-matching/1.题目解析

给定s串和p串,问p串是否能和s串完全匹配

匹配规则:

1.'?'可以匹配任意单个字符

2.'*'可以匹配任意字符序列(多个字符, 包括空字符串也能匹配)

3.其他字符一一对应即可

eg:   s = "abcdef"   p = "*a*f"

这两个字符串就是可以匹配的,让第二个字符串的'*'匹配空串,'a'匹配'a', '*'匹配"bcde", 'f'匹配'f'即可

2.算法分析

1.状态表示

dp[i][j]: p[0, j] 区间内的子串是否能匹配 s[0, i] 区间内的子串
2.状态转移方程

上图是根据最后一个位置的状态划分问题得到的状态转移方程,但显然p[j]=='*'时,还需要一层for循环求解, 因此时间复杂度是O(N^3),  因此我们可以进一步优化, 将p[j]=='*'时的状态转移方程用若干个有限的状态表示

3.初始化

添加一行一列

dp[0][0] = true, 因为两个空串是匹配的

第一行的其他元素: 

第一行是i = 0, 表示s串是空串,而p串不是空串,因此想要p串匹配s串,p串必须全为连续的'*'字符。 所以写一个for循环,j遍历,当p[j]是'*'时, dp表的该位置是true, 否则后续全为false

第一列的其他元素:

p串为空,s串不为空,无法匹配, 值为false

4.填表顺序

从上往下填表,每一行从左往右

5.返回值

dp[m][n]

3.算法代码

class Solution {
public:
    bool isMatch(string s, string p) 
    {
        int m = s.size(), n = p.size();
        s = "_" + s, p = "_" + p;
        //1.创建dp表
        vector<vector<bool>> dp(m+1, vector<bool>(n+1));
        //2.初始化
        dp[0][0] = true;
        for(int j = 1; j <= n; j++)
            if(p[j] == '*') dp[0][j] = true;
            else break;
        //3.填表
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(p[j] == '*') 
                    dp[i][j] = dp[i][j-1] || dp[i-1][j];
                else 
                    dp[i][j] = (p[j] == '?' || s[i] == p[j]) && dp[i-1][j-1];
            }
        }
        //4.返回值
        return dp[m][n];
    }
};

五、正则表达式匹配

10. 正则表达式匹配 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/regular-expression-matching/1.题目解析

给定s串和p串,请你判断s串和p串是否能匹配

匹配规则:

1.'.'匹配任意单个字符

2.'*'要结合前面的一个字符来解析,比如"a*"可以表示空串,可以表示a, 可以表示aa, 可以表示aaa等等

3.'*'前面如果还是一个'*',  是无效的,因为无法被解析, 题目保证了不会出现这种情况

题目保证了字符串中只有小写字母和'.'与'*'

2.算法分析

1.状态表示

dp[i][j]: p[0, j] 区间内的子串是否能匹配 s[0, i] 区间内的子串

2.状态转移方程

3.初始化

添加一行一列

dp[0][0] = true, 因为两个空串是匹配的

第一行的其他元素: 

第一行是i = 0, 表示s串是空串,而p串不是空串,因此想要p串匹配s串: p串必须是:

_*_*_*的形式,假如第一个位置是1,也就是要求连续的偶数位置都是*, 此时才能匹配,当某一个偶数为不是*了,后续的dp值都是false

第一列的其他元素:

p串为空,s串不为空,无法匹配, 值为false

4.填表顺序

从上往下填表,每一行从左往右

5.返回值

dp[m][n]

3.算法代码

class Solution {
public:
    bool isMatch(string s, string p) 
    {
        int m = s.size(), n = p.size();
        s = "_" + s, p = "_" + p;
        //1.创建dp表
        vector<vector<bool>> dp(m+1, vector<bool>(n+1));
        //2.初始化
        dp[0][0] = true;
        for(int j = 2; j <= n; j += 2)
            if(p[j] == '*') dp[0][j] = true;
            else break;
        //3.填表
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(p[j] == '*') 
                    dp[i][j] = dp[i][j-2] || ((p[j-1] == '.' || p[j-1] == s[i]) && dp[i-1][j]);
                else 
                    dp[i][j] = (p[j] == s[i] || p[j] == '.') && dp[i-1][j-1];
            }
        }
        //4.返回值
        return dp[m][n];
    }
};

、交错字符串

97. 交错字符串 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/interleaving-string/1.题目解析

给定字符串s1, s2, s3, 判断s1和s2能否交错构成s3

2.算法分析

先预处理一下,将三个字符串的前面加上'_'

1.状态表示

dp[i][j]: s1中 [1, i] 区间内的字符串以及 s2[1, j] 区间内的字符串,能拼接成 s3[1, i+j] 区间内的字符串

2.状态转移方程

3.初始化

dp[0][0] = true, 表示两个空串可以拼接成空串

第一行的其余位置:

s1为空,s2不为空,所以当s2对应位置字符和s3对应位置字符相等时,就是true, 某个位置不相等,则后续的dp值都是false

第一列的其余位置:

s2为空,s1不为空,所以当s1对应位置字符和s3对应位置字符相等时,就是true, 某个位置不相等,则后续的dp值都是false

4.填表顺序

从上往下填表,每一行从左往右

5.返回值

dp[m][n]

3.算法代码

class Solution {
public:
    bool isInterleave(string s1, string s2, string s3) 
    {
        //1.创建dp表
        int m = s1.size(), n = s2.size();
        if(m + n != s3.size()) return false; //s1长度+s2长度 != s3长度,直接返回false
        s1 = " " + s1, s2 = " " + s2, s3 = " " + s3;
        vector<vector<bool>> dp(m+1, vector<bool>(n+1));
        //2.初始化dp表
        dp[0][0] = true;
        for(int j = 1; j <= n; j++)
            if(s2[j] == s3[j]) dp[0][j] = true;
            else break;
        for(int i = 1; i <= m; i++)
            if(s1[i] == s3[i]) dp[i][0] = true;
            else break;
        //3.填表
        for(int i = 1; i <= m; i++)
            for(int j = 1; j <= n; j++)
                dp[i][j] = (s1[i] == s3[i+j] && dp[i-1][j]) || (s2[j] == s3[i+j] && dp[i][j-1]);
        //4.返回值
        return dp[m][n];
    }
};

七、两个字符串的最小ASCII删除和

712. 两个字符串的最小ASCII删除和 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings/description/1.题目解析

给定两个字符串,每个字符串都可以删除若干字符,删除之后使得两个字符串相等,求删除的字符的最小的ASCII值之和

2.算法分析

正难则反: 求两个字符串里面的所有公共子序列里面,ASCII值的最大和

1.状态表示

dp[i][j]: s1的 [0, i] 区间以及 s2的 [0, j] 区间内的所有子序列里,公共子序列的ASCII的最大和

2.状态转移方程

3.初始化

添加一行一列, 全都初始化0,因为任何一个字符串为空,公共子序列的ASCII之和都是空
4.填表顺序

从上往下填表,每一行从左往右

5.返回值

两个字符串的ASCII之和 - dp[m][n] * 2

3.算法代码

class Solution {
public:
    int minimumDeleteSum(string s1, string s2) 
    {
        //1.创建dp表 + 初始化
        int m = s1.size(), n = s2.size();
        s1 = " " + s1, s2 = " " + s2;
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        //2.填表
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
                if(s1[i] == s2[j])
                    dp[i][j] = max(dp[i][j], dp[i-1][j-1] + s1[i]);
            }
        }
        //3.返回值
        int sum = 0;
        for(int i = 1; i <= m; i++)
            sum += s1[i];
        for(int j = 1; j <= n; j++)
            sum += s2[j];
        return sum - dp[m][n] * 2;
    }
};

八、最长重复子数组

718. 最长重复子数组 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/maximum-length-of-repeated-subarray/1.题目解析

找出两个数组中的最长重复子数组, 返回长度

2.算法分析

1.状态表示

dp[i][j]: nums1中以 i 位置元素为结尾的所有子数组以及nums2中以 j 位置元素为结尾的所有子数组中,最长重复子数组的长度

2.状态转移方程

3.初始化

添加一行一列,全部初始化成0(因为任何一个子数组为空,最长重复子数组长度就是0)

4.填表顺序

从上往下填表

5.返回值

dp表的最大值

3.算法代码
 

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) 
    {
        //1.创建dp表
        int m = nums1.size(), n = nums2.size();
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        //2.填表
        int ret = 0;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(nums1[i-1] == nums2[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
                ret = max(ret, dp[i][j]);
            }
        }
        //3.返回值
        return ret;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值