C++算法 —— 动态规划(7)两个数组的dp

这篇具有很好参考价值的文章主要介绍了C++算法 —— 动态规划(7)两个数组的dp。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


每一种算法都最好看完第一篇再去找要看的博客,因为这样会帮你梳理好思路,看接下来的博客也就更轻松了。当然,我也会尽量在写每一篇时都可以不懂这个算法的人也能边看边理解。

1、动规思路简介

动规的思路有五个步骤,且最好画图来理解细节,不要怕麻烦。当你开始画图,仔细阅读题时,学习中的沉浸感就体验到了。

状态表示
状态转移方程
初始化
填表顺序
返回值

动规一般会先创建一个数组,名字为dp,这个数组也叫dp表。通过一些操作,把dp表填满,其中一个值就是答案。dp数组的每一个元素都表明一种状态,我们的第一步就是先确定状态。

状态的确定可能通过题目要求来得知,可能通过经验 + 题目要求来得知,可能在分析过程中,发现的重复子问题来确定状态。还有别的方法来确定状态,但都大同小异,明白了动规,这些思路也会随之产生。状态的确定就是打算让dp[i]表示什么,这是最重要的一步。状态表示通常用某个位置为结尾或者起点来确定,但两个数组的问题则不是这样,要选取第一个字符串[0, i]区间以及第二个字符串[0, j]区间作为研究对象。

状态转移方程,就是dp[i]等于什么,状态转移方程就是什么。像斐波那契数列,dp[i] = dp[i - 1] + dp[i - 2]。这是最难的一步。一开始,可能状态表示不正确,但不要紧,大胆制定状态,如果没法推出转移方程,没法得到结果,那这个状态表示就是错误的。所以状态表示和状态转移方程是相辅相成的,可以帮你检查自己的思路。

要确定方程,就从最近的一步来划分问题。

初始化,就是要填表,保证其不越界。像第一段所说,动规就是要填表。比如斐波那契数列,如果要填dp[1],那么我们可能需要dp[0]和dp[-1],这就出现越界了,所以为了防止越界,一开始就固定好前两个值,那么第三个值就是前两个值之和,也不会出现越界。初始化的方式不止这一点,有些问题,假使一个位置是由前面2个位置得到的,我们初始化最一开始两个位置,然后写代码,会发现不够高效,这时候就需要设置一个虚拟节点,一维数组的话就是在数组0位置处左边再填一个位置,整个dp数组的元素个数也+1,让原先的dp[0]变为现在的dp[1],二维数组则是要填一列和一行,设置好这一行一列的所有值,原先数组的第一列第一行就可以通过新填的来初始化,这个初始化方法在下面的题解中慢慢领会。

第二种初始化方法的注意事项就是如何初始化虚拟节点的数值来保证填表的结果是正确的,以及新表和旧表的映射关系的维护,也就是下标的变化。

填表顺序。填当前状态的时候,所需要的状态应当已经计算过了。还是斐波那契数列,填dp[4]的时候,dp[3]和dp[2]应当都已经计算好了,那么dp[4]也就出来了,此时的顺序就是从左到右。还有别的顺序,要依据前面的分析来决定。

返回值,要看题目要求。

这篇博客需要从头开始看,后面的题会用到前面的思路

2、最长公共子序列

1143. 最长公共子序列

C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++

确定状态。让dp[i][j]表示s1的[0, i]区间以及s2的[0, j]区间内所有的子序列中,最长公共子序列的长度。

如果s1和s2最后一个字符相同,那么最长公共子序列一定是以这个字符为结尾的。如果公共子序列在s1和s2内部,那么再连接最后一个字符才是最长公共子序列;如果s1的公共子序列从某个字符到最后一个字符,而s2的公共序列在内部,那么既然是公共序列,最后一个字符一定相同,那么s2就可以连接最后一个字符。

i和j从字符串的末尾开始,如果s[i] == s[j],那么只需要在0 ~ i - 1和0 ~ j - 1找到公共子序列,然后+1就可。如果s[i] != s[j],那么最长公共子序列一定不以这两个字符结尾,这样的话我们就可以在s1的[0, i - 1]和s2的[0, j]里找公共子序列,或者[0, i],[0, j - 1],或者[0, i - 1]和[0, j - 1],也就是3种情况,实际上前两个包含最后一个情况[0, i - 1],[0, j - 1],但不干扰最后最后结果,因为我们要的是最长长度,重复的问题不需要考虑,只要能保证不漏掉情况就行,只是还是可以优化,就不去考虑第三种情况。如果题目要求求最长公共子序列个数,那么第3种情况肯定要去掉,因为已经包含了。

初始化。空串是有研究意义的,因为空串也是公共子序列,只是长度为0,并且因为空串的存在,也方便初始化。 二维数组dp表,多添加一行一列,让原先的[0][0]变成现在的[1][1],现在的第一行表示s1为空的情况,第一列表示s2为空的情况。为了保证添加的一行一列的值保证后续填表是正确的,以及下标的映射关系,我们对于字符串可以在最前面加个空字符就行。

填表顺序,因为需要i - 1,j - 1,所以从上到下,每一行从左到右。返回值是最大值,也就是dp表右下角的值。

    int longestCommonSubsequence(string s1, string s2) {
        int m = s1.size(), n = s2.size();
        s1 = " " + s1, s2 = " " + s2;
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        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;//如果前面没有加那个空字符,那么这里就得些s1[i - 1] == s2[j - 1],保证下标对齐
                else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return dp[m][n];
    }

3、不相交的线

1035. 不相交的线

C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++
C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++

这道题建立在公共子序列的思路基础上。不交叉的线,也就是上面选中的数字的顺序,下面选中的数字也得按照这个顺序选中,这样就可以保证符合要求。其实这就是在找公共子序列的问题,序列中的元素可以不连续,但顺序不会变,左边的元素都比右边元素靠前,所以这道题就转化为了最长公共子序列的长度。

定义dp[i][j]为nums1里的[0, i]区间以及nums2里的[0, j]区间里面的所有的子序列中,最长公共子序列的长度,因为题目要求求长度。按照公共子序列的思路,可以分为两种情况,n1[i] == n2[j],两个数字相等,那就找dp[i - 1][j - 1],因为这里存的是n1到第i - 1个数字,n2到第j - 1个数字的最长的公共子序列,然后 + 1就是dp[i][j]应当存的值。如果n1[i] != n2[j],那么就转换为三种情况,找到do[i - 1][j],dp[i][j - 1],dp[i - 1][j - 1]的最大值当作dp[i][j]的值,因为前两个包含了dp[i - 1][j - 1]的情况,所以只考虑前两个。这样dp表的填写就完成了。

由于我们需要i - 1,j - 1,所以在原本的dp表左上角加上一行一列,里面的数字要保证不影响后续的填表以及下标的映射关系,之前[0, 0]变成了[1, 1],所以新增的行列里的值都变成0,因为我们要取最大值,所以这样就不影响。填表顺序是从上到下,从左到右。返回值是dp[n1][n2]。

    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        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][j - 1], dp[i - 1][j]);
            }
        }
        return dp[m][n];
    }

4、不同的子序列

115. 不同的子序列

C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++

根据题目要求,得在s中找t。由于是子序列,所以不必让t完整地出现,看看示例就明白了。既然是子序列,按照之前的思路,我们还是得看看最后一个元素的情况,以及需要用到dp表中前面的值。

先定义dp[i][j]表示s的[0, j]区间内的所有子序列中,有多少个t的[0, i]区间内的子串。注意一个是子序列,一个是子串,这两个的含义需要分清,一个不需要连续,一个必须连续的字符。接下来确定状态转移方程。s的子序列中有两个情况,包含s[j]和不包含。如果包含s[j],要是想包含t[0, i]区间的子串,就得让s[j] == t[i]才可行,这两个字符相等了,那就再找dp[i - 1][j - 1],不需要 + 1,因为求的是个数,不是长度。如果不包含s[j],那么就往前一步,变成[0, j - 1]里找t,也就是dp[i][j - 1]。因为找的是个数,所以dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1]。

初始化的时候,因为有i - 1和j - 1,新增一行列比较好,新增在左上角。第一行,代表dp[0][j],也就是说t是空串,那么这一行应当都初始化为1,因为肯定包含空串;第一列,dp[i][0],s是空串,那么就不包含t了,应当初始化为0,而dp[0][0]这个位置,两个都是空串,应当为1,这点在上面初始化第一行时就做好了。填表顺序是从上到下,从左到右。返回值就是dp[m][n]。

    int numDistinct(string s, string t) {
        int m = t.size(), n = s.size();
        vector<vector<double>> dp(m + 1, vector<double>(n + 1));//long和long long都不行,溢出
        for(int j = 0; j <= n; j++) dp[0][j] = 1;
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                dp[i][j] += dp[i][j - 1];
                if(t[i - 1] == s[j - 1]) dp[i][j] += dp[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }

5、通配符匹配

44. 通配符匹配

C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++

像abcde和ae这样,星号匹配空字符,星号匹配bcd都是可以的。

根据经验,两个字符串分别选取[0, i]和[0, j]区间。题目中是能否匹配成功,那么我们就定义dp[i][j]是p[0, j]区间的子串能否匹配s[0, i]区间内的子串,类型是bool。

状态转移方程。还是根据最后一个位置的状况来确定问题。因为是通配符,每个位置都有三种情况。如果最后一个位置是普通字符,那么如果p[j] == s[i],就看dp[i - 1][j - 1]是否为true;如果最后一个位置是?,它可以直接匹配一个字符,然后再看dp[i - 1]
[j - 1];如果最后一个位置是星号,星号可以匹配很多种,匹配空字符,就看dp[i][j - 1],匹配1个字符,就看dp[i - 1][j - 1],匹配2个字符,就看dp[i - 2][j - 1]。星号的情况如何实现到代码上?dp[i][j] = dp[i - 0][j - 1] || dp[i - 1][j - 1] || dp[i - 2][j - 1],如果i变为i - 1,那么dp[i - 1][j] = dp[i - 1][j - 1] || dp[i - 2][j - 1] || dp[i - 3][j - 1],所以可以发现dp[i][j]就等于dp[i][j - 1] || dp[i - 1][j],后面的所有情况都包含在dp[i - 1][j]中。

初始化,因为有i - 1,j - 1,我们就需要在左上角新增一行一列。新增行列的初始化需要做一下分析,第一个位置dp[0][0],空串匹配空串,这肯定是true;第一列表示dp[i][0],p是空串,那它不能匹配字符串,所以第一列除了第一个元素,其它都是false;第一行,也就是dp[0][j],s是空串,p只有是星号才能是true,不是的话就是false。下标映射关系也需要分析一下,我们可以按照之前的做法,下标减1,而因为是字符串,也可以在字符串之前加一个空字符。

填表顺序是从上到下,从左到右。返回值就是右下角那个值。

    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));
        s = " " + s, p = " " + p;
        dp[0][0] = true;
        for(int j = 1; j <= n; j++)
        {
            if(p[j] == '*') dp[0][j] = true;
            else break;
        }
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(p[j] == '*') dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
                else dp[i][j] = (p[j] == '?' || s[i] == p[j]) && dp[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }

6、正则表达式匹配

10. 正则表达式匹配

C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++

这里的星号和上一个题不一样,星号不能连续,任意一个字符搭配星号可以变成空串。还是选取s[0, i]区间,p[0, j]区间。dp[i[j]表示p[0, j]区间内的子串是否能够匹配s[o, i]区间内的子串,所以是bool。

状态转移方程。还是根据最后一个位置的状态来分析。先看最后一个位置,如果p[j] == s[i],那就看dp[i - 1][j - 1]是否为true,那么dp[i][j]就是true;如果p[j]是点号,那肯定没问题;如果是星号,它得看前面一个值,前面是点号,那么这两个字符可以转换成0个以及多个点号,空串就得看dp[i][j - 2],一个点号就看dp[i - 1][j - 2],转换成2个点号就是dp[i - 2][j - 2],以此类推,只要有一个为true,那么dp[i][j]就是true,dp[i][j]是这样,那么dp[i - 1][i]也能表示出来,所以最后能得到dp[i][j] = dp[i][j - 2] || dp[i - 1][j]。如果p[j]是星号,p[j - 1]是普通字符,那么有两种情况,可以匹配成空串,那就看dp[i][j - 2],如果匹配一个,也就是p[j - 1]和s[i]相等的话,再就看dp[i - 1][j]是否为true。

所以方程应当是这样的:p[j] == s[i]或者p[j]是一个点,那就看dp[i - 1][j - 1]是否true,dp[i][j]才为true。如果p[j]是星号,有两种大情况,每个情况都有匹配成空串的情况,都是dp[i][j - 2],以及还要看是点还是普通字符,再看dp[i - 1][j]。

初始化时最前面引入空串,新增行列,管理好下标的映射关系。dp[0][0]是true,第一列其余部分就是false,因为第一列表示p是空串,这肯定不能匹配;第一行是s为空串,p可以任意一个字符搭配星号,多个这样的组合也行,只要偶数位置是星号就行。

填表顺序是从上到下,从左到右。返回值是dp[m][n]。

    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));
        s = ' ' + s, p = ' ' + p;
        dp[0][0] = true;
        for(int j = 2; j <= n; j += 2)
        {
            if(p[j] == '*') dp[0][j] = true;
            else break;
        }
        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];
            }
        }
        return dp[m][n];
    }

7、交错字符串

97. 交错字符串

C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++
C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++

s3是由s1和s2中的序列构成的,长度等于这两个字符串之和。为了能让分析更清楚,三个字符串前面都加上一个空字符,这样就是s1[1, i],s2[1, j],s3[1, i+j]区间做分析。

把dp[i][j]表示为s1[1, i]区间内的字符串以及s2[1, j]区间内的字符串能否拼接凑成s3[1, i+j]区间内的字符串,类型就是bool。

状态转移方程。根据最后一个位置来分析。如果s3[i + j]处的结尾字符有可能是s1的,也有可能是s2的,所以分为两种情况,如果是s1,如果s1[i] == s3[i + j],那么就看dp[i - 1][j]是否为true,s2也同理,看dp[i][j - 1]是否为true。

初始化时,在左上角新增一行一列,dp[0][0],也就是3个字符串都为0,就是true。第一列,也就是s2为空时,dp[i][0],就看s1和s3之间哪个位置字符相同,哪个就是true,不是就之后所有位置都是false;第一行也是如此,比较s2和s3。填表顺序是从上到下,从左到右。返回值是dp[m][n]。

    bool isInterleave(string s1, string s2, string s3) {
        int m = s1.size(), n = s2.size();
        if(m + n != s3.size()) return false;
        s1 = ' ' + s1, s2 = ' ' + s2, s3 = ' ' + s3;
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));
        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;
        }
        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]);
            }
        }
        return dp[m][n];
    }

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

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

C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++

仔细分析示例会发现,两个字符串有多种删除法,得到不同的结果字符串,但是删除的字符的ASCII码值最小的结果,是s1和s2中的公共子序列,并且还是ASCII值最大的那个,所以这个问题就变成了找公共子序列中ASCII值最大的。

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

状态转移方程。看最后一个位置来分析,如果s[i] == s[j],那么就看dp[i - 1][j - 1],然后再加上这个位置的值即可;如果不相等,那么就变成两种情况,s1以i - 1位置结尾来分析dp[i - 1][j]和s2以j - 1位置为结尾来分析dp[i][j - 1]。

初始化时,左上角新增一行一列,要管理好下标的映射关系,新增行列初始化为0。

填表顺序是从上到下,从左到右。最大值是dp[m][n],然后用2个字符串的ASCII和来减去两倍的dp[m][n],因为两个字符串都要减。

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

9、最长重复子数组

718. 最长重复子数组

C++算法 —— 动态规划(7)两个数组的dp,C++算法,动态规划,算法,c++

如果以[0, i]区间来分析,找子数组,这个角度就不行,因为子数组和子序列不同,它必须是连续的,[0, i]区间内的最长子数组可能不以i结尾,而是以前面的某个位置结尾,所以就无法确定最长长度。这题的状态表示应当改为dp[i][j]是nums1中以i位置元素为结尾的所有子数组和nums2中以j位置元素为结尾的所有子数组中最长重复子数组的长度。

状态转移方程。以最后一个位置来分析。如果nums1[i] != nums[j],那此时就不是重复的子数组,如果相等的话,结尾位置就没问题了,那就再看前面一个位置,所以应当是dp[i - 1][j - 1] + 1。

初始化时,新增一行一列,注意下标的映射关系,新增的行列应当全为0。

填表顺序是从上到下,从左到右。返回值是最大值。

    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size(), n = nums2.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        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]);
                }
            }
        }
        return ret;
    }

结束。文章来源地址https://www.toymoban.com/news/detail-729321.html

到了这里,关于C++算法 —— 动态规划(7)两个数组的dp的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++动态规划-线性dp算法

    莫愁千里路 自有到来风 CSDN 请求进入专栏                                    X 是否进入《 C++ 专栏》? 确定 目录  线性dp简介 斐波那契数列模型  第N个泰波那契数 思路: 代码测试:  三步问题 思路: 代码测试: 最小花费爬楼梯 思路: 代码测试:  路径问题 数字三

    2024年02月19日
    浏览(48)
  • c++ 算法之动态规划—— dp 详解

    dp 是 c++ 之中一个简单而重要的算法,每一个 OIer 必备的基础算法,你知道它究竟是什么吗? 目录 一、简介         1.为什么不能贪心?         2.揭秘 dp   二、应用         1.定义         2.边界值         3.动态转移方程         4.结果         5.代码

    2024年04月09日
    浏览(48)
  • C++ DP算法,动态规划——背包问题(背包九讲)

    有N件物品和一个容量为 V V V 的背包。放入第i件物品耗费的空间是 C i C_i C i ​ ,得到的价值是 W i W_i W i ​ 。 求解将哪些物品装入背包可使价值总和最大。 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 用子问题定义状态:即 F [ i , v ] F[i, v] F

    2024年02月16日
    浏览(51)
  • 【数位dp】【动态规划】C++算法:233.数字 1 的个数

    视频算法专题 动态规划汇总 给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。 示例 1: 输入:n = 13 输出:6 示例 2: 输入:n = 0 输出:0 提示: 0 = n = 10 9 本题比较简单,主要讲封装类。m_vPre记录上一位所有状态,程序结束时,记录的是最后一位的所有

    2024年01月16日
    浏览(42)
  • 「算法小记」-2:矩阵链相乘的方案数【迭代/递归/动态规划/区域化DP/记忆化搜索】(C++ )

    😎 作者介绍:我是程序员洲洲,一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公粽号:程序员洲洲。 🎈 本文专栏:本文收录于洲洲的《算法小记》系列专栏,该专栏记录了许

    2024年02月05日
    浏览(54)
  • 【动态规划】【数学】【C++算法】805 数组的均值分割

    视频算法专题 动态规划汇总 数学 给定你一个整数数组 nums 我们要将 nums 数组中的每个元素移动到 A 数组 或者 B 数组中,使得 A 数组和 B 数组不为空,并且 average(A) == average(B) 。 如果可以完成则返回true , 否则返回 false 。 注意:对于数组 arr , average(arr) 是 arr 的所有元素的和

    2024年02月20日
    浏览(44)
  • 【动态规划】【滑动窗口】【C++算法】 629K 个逆序对数组

    视频算法专题 动态规划汇总 C++算法:滑动窗口总结 逆序对的定义如下:对于数组 nums 的第 i 个和第 j 个元素,如果满足 0 = i j nums.length 且 nums[i] nums[j],则其为一个逆序对;否则不是。 给你两个整数 n 和 k,找出所有包含从 1 到 n 的数字,且恰好拥有 k 个 逆序对 的不同的数

    2024年01月17日
    浏览(44)
  • 从01背包开始动态规划:暴力解法 + dp + 滚动数组 + dp优化

        01背包问题是动态规划中最经典的问题之一,本篇将通过给出其四种解法,使读者更深刻理解动态规划。   有N件物品和一个容量是 V 的背包,每个物品有各自的体积和价值,且每个物品只能放一次(这也是01背包名字的由来),如何让背包里装入的物品具有最大的价值总

    2024年04月17日
    浏览(54)
  • 【动态规划】两个数组问题

    题目链接 状态表示 dp[i][j] 表示 s1 0 到 i 区间内,以及时s2 0 到 j 区间内所有的子序列中,最长的公共子序列 状态转移方程 根据最后一个位置的请款分类讨论。 初始化 关于字符串的dp问题,空串是有研究意义的。引入空串的概念之后会方便我们的初始化 这里还可以使用之前

    2024年02月11日
    浏览(35)
  • DP算法:动态规划算法

    (1)确定初始状态 (2)确定转移矩阵,得到每个阶段的状态,由上一阶段推到出来 (3)确定边界条件。 蓝桥杯——印章(python实现) 使用dp记录状态,dp[i][j]表示买i张印章,凑齐j种印章的概率 i表示买的印章数,j表示凑齐的印章种数 情况一:如果ij,不可能凑齐印章,概

    2024年02月07日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包