两个数组的动态规划

两个数组的动态规划

最长公共子序列

题目描述

最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 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 。

提示:

  • 1 <= text1.length, text2.length <= 1000
  • text1 和 text2 仅由小写英文字符组成。

递归

这题实际上和最长回文子序列没什么两样,我们从递归出发推导最优的动态规划。

首先我们对于两个字符串,我们只判断他的首元素是否相等,如果相等:

在这里插入图片描述


不难发现,我们选取z作为两者的公共子序列开头是不会影响任何后续选取,为最优情况。
所以两个字符串最长公共子序列长度,变成了1+去掉首元素部分的最长公共子序列长度,然后继续比较首元素。

在这里插入图片描述

接下来是第二种情况:

在这里插入图片描述


首元素不相等,那么他们的最长公共子序列有三种情况:

  1. x开头
  2. z开头
  3. 非x非z开头

首先是x开头,那么下面字符串的首元素已经不再影响后续公共子串选取可以去掉了:

在这里插入图片描述


现在变成了这两个字符串的最长公共子序列。

第二种情况是z开头,同理上面字符串首元素可以去掉:

在这里插入图片描述


最后是第三种情况,两个首元素都不影响最终结果可以去掉:

在这里插入图片描述


我们用递归的方式实现:

classSolution{public:intlongestCommonSubsequence(string text1, string text2){int n=text1.size(),m=text2.size();if(!n||!m)return0;if(text1[0]==text2[0])returnlongestCommonSubsequence(text1.substr(1),text2.substr(1))+1;elsereturnmax(longestCommonSubsequence(text1.substr(1),text2),longestCommonSubsequence(text1,text2.substr(1)));}};

记忆化

直接递归搜索会导致叶子部分重复搜索指数次,导致超时。
所以我们考虑记录第一次搜索后的结果,后续再次搜索直接返回即可:

classSolution{public:intlongestCommonSubsequence(string text1, string text2){int n=text1.size(),m=text2.size(); vector<vector<int>>hash(n,vector<int>(m,-1)); function<int(int,int)>dfs=[&](int i,int j){if(i==n||j==m)return0;if(hash[i][j]<0){if(text1[i]==text2[j]){ hash[i][j]=1+dfs(i+1,j+1);}else{ hash[i][j]=max(dfs(i,j+1),dfs(i+1,j));}}return hash[i][j];};returndfs(0,0);}};

时间复杂度O(mn),空间复杂度O(mn).

动态规划

其实刚才我们已经将动态规划都推导出来了。
我们定义dp[i][j]为text1以i为起点,text2以j为起点二者的子串的最长公共子序列长度。
那么dp[i][j]依赖于dp[i+1][j+1]、dp[i+1][j]、dp[i][j+1]
因此我们从右下角开始填表。

此外当dp在最后一行和最后一列时,有越界风险,我们大可以多开一行一列的空间来避免越界和初始化,这也是动态规划常用技巧。

classSolution{public:intlongestCommonSubsequence(string text1, string text2){int n=text1.size(),m=text2.size(); vector<vector<int>>dp(n+1,vector<int>(m+1));for(int i=n-1;i>=0;--i){for(int j=m-1;j>=0;--j){if(text1[i]==text2[j]){ dp[i][j]=1+dp[i+1][j+1];}else{ dp[i][j]=max(dp[i+1][j],dp[i][j+1]);}}}return dp[0][0];}};

时间复杂度O(mn),空间复杂度O(mn)

空间优化

注意到我们每次更新只需要下一行的状态,因此可以空间压缩成一行:

classSolution{public:intlongestCommonSubsequence(string text1, string text2){int n=text1.size(),m=text2.size(),cur,prev;if(n<m){swap(n,m);swap(text1,text2);} vector<int>dp(m+1);for(int i=n-1;i>=0;--i){ prev=0;for(int j=m-1;j>=0;--j){ cur=dp[j];if(text1[i]==text2[j]){ dp[j]=1+prev;}else{ dp[j]=max(dp[j],dp[j+1]);} prev=cur;}}return dp[0];}};

时间复杂度O(mn),空间复杂度O(min(m,n))。

实际上数据有范围的话我们还可以进一步优化:

classSolution{public:staticint dp[1001];intlongestCommonSubsequence(string text1, string text2){int n=text1.size(),m=text2.size(),cur,prev;memset(dp,0,sizeof(int)*(m+1));for(int i=n-1;i>=0;--i){ prev=0;for(int j=m-1;j>=0;--j){ cur=dp[j];if(text1[i]==text2[j]){ dp[j]=1+prev;}else{ dp[j]=max(dp[j],dp[j+1]);} prev=cur;}}return dp[0];}};int Solution::dp[1001]{0};
在这里插入图片描述

不相交的线

题目描述

不相交的线
在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。

现在,可以绘制一些连接两个数字 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

提示:

  • 1 <= nums1.length, nums2.length <= 500
  • 1 <= nums1[i], nums2[j] <= 2000

算法原理和实现

我的论文创新点be like:将最长公共子序列包装成不相交的线。

这两题就是一样的解法,这里不多做赘述:

classSolution{public:staticint dp[501];intmaxUncrossedLines(vector<int>& nums1, vector<int>& nums2){int n=nums1.size(),m=nums2.size(),cur,prev;memset(dp,0,sizeof(int)*(m+1));for(int i=n-1;i>=0;--i){ prev=0;for(int j=m-1;j>=0;--j){ cur=dp[j];if(nums1[i]==nums2[j]){ dp[j]=prev+1;}else{ dp[j]=max(cur,dp[j+1]);} prev=cur;}}return dp[0];}};int Solution::dp[501]{0};

时间复杂度O(mn),空间复杂度O(n)

不同的子序列

题目描述

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

测试用例保证结果在 32 位有符号整数范围内。

示例 1:

输入:s = “rabbbit”, t = “rabbit”
输出:3
解释:
如下所示, 有 3 种可以从 s 中得到 “rabbit” 的方案。
rabbbit
rabbbit
rabbbit

示例 2:

输入:s = “babgbag”, t = “bag”
输出:5
解释:
如下所示, 有 5 种可以从 s 中得到 “bag” 的方案。
babgbag
babgbag
babgbag
babgbag
babgbag

提示:

  • 1 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成

算法原理和实现

老实说这题和上面两题没多少区别,不过我已经厌倦了从右下角开始填表,我决定这次从左上角开始填表.
dp[i][j]表示s以i为结尾的子序列中有t以j为结尾的子串的数目.

那么s中[0,i]的子序列分两种情况:

  1. 以i为结尾,我们就要判断s[i]是否与t[j]相等,如果相等dp[i][j]+=dp[i-1][j-1]
  2. 不以i为结尾,那直接去掉即可dp[i][j]+=dp[i-1][j]

具体实现:

classSolution{public:intnumDistinct(string s, string t){int n=s.size(),m=t.size();if(n<m)return0; vector<vector<unsignedlonglong>>dp(n+1,vector<unsignedlonglong>(m+1));for(int i=0;i<=n;++i)dp[i][0]=1;for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){ dp[i][j]=dp[i-1][j];if(s[i-1]==t[j-1]) dp[i][j]+=dp[i-1][j-1];}}return dp[n][m];}};

时间复杂度和空间复杂度都是O(mn).
这里官解应该是出了点问题,我们要用无符号长长整型才不会溢出.
同理我们可以进行空间优化:

classSolution{public:intnumDistinct(string s, string t){int n=s.size(),m=t.size(),cur,prev;if(n<m)return0; vector<unsignedlonglong>dp(m);for(int i=0;i<n;++i){ prev=1;for(int j=0;j<m;++j){ cur=dp[j];if(s[i]==t[j]) dp[j]+=prev; prev=cur;}}return dp[m-1];}};

时间复杂度O(mn),空间复杂度O(m).

通配符匹配

题目描述

通配符匹配
给你一个输入字符串 (s) 和一个字符模式 § ,请你实现一个支持 ‘?’ 和 ‘’ 匹配规则的通配符匹配:
‘?’ 可以匹配任何单个字符。
'
’ 可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

示例 1:

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

示例 2:

输入:s = “aa”, p = “*”
输出:true
解释:‘*’ 可以匹配任意字符串。

示例 3:

输入:s = “cb”, p = “?a”
输出:false
解释:‘?’ 可以匹配 ‘c’, 但第二个 ‘a’ 无法匹配 ‘b’。

提示:

  • 0 <= s.length, p.length <= 2000
  • s 仅由小写英文字母组成
  • p 仅由小写英文字母、‘?’ 或 ‘*’ 组成

记忆化搜索

这题的难点在于’*'字符的匹配.

我们考虑两字符串的最后一个元素是否相等.

如果模式串最后一个字符是’*‘,那么’*‘可以匹配任意字符:

在这里插入图片描述


case1我们只需要将模式串的星号去掉即可,case 2则是两个字符串的末尾元素.我们再看case3 原串去掉两个末尾字符,后两种情况可以合并,具体怎么做呢?
我们只需要舍弃原串字符而不舍弃’*'即可:

在这里插入图片描述

如果是两个普通字符且相等或者模式串最后一个字符是’?':

在这里插入图片描述


那这两个字符就不会影响最终结果,可以舍去,我们判断前面字符是否相等即可:

在这里插入图片描述

如果是两个普通字符且不相等:

在这里插入图片描述


那我们知道这两个字符串一定不相等.

可以看到我们case2递归匹配剩余字符后,如果直接走case1相当于’*‘匹配一个字符,如果走case2再走case1,相当于’*'匹配两个字符…

此外我们可以用一个哈希表记录搜索过的结果:

classSolution{public:boolisMatch(string s, string p){int n=s.size(),m=p.size(); vector<vector<int>>hash(n,vector<int>(m)); function<bool(int,int)> dfs =[&](int i,int j){if(i<0){for(int k=j;k>=0;--k){if(p[k]!='*')returnfalse;}returntrue;}elseif(j<0)returnfalse;if(!hash[i][j]){bool tmp; hash[i][j]=1;if(p[j]=='?')tmp=dfs(i-1,j-1);elseif(p[j]=='*')tmp=dfs(i-1,j)||dfs(i,j-1);elseif(p[j]==s[i])tmp=dfs(i-1,j-1);else tmp=false; hash[i][j]+=tmp;}if(hash[i][j]==2)returntrue;elsereturnfalse;};returndfs(n-1,m-1);}};

时间复杂度O(mn),空间复杂度O(mn)

动态规划

根据记忆化搜索的分析.我们定义状态表示为:
dp[i][j]:s以i结尾的前缀和p以j为结尾的前缀能否匹配.
因此我们从左上角开始填表.

  • 初始化:
    s的前缀为空串时,p的前缀必须全是’*'才能匹配.
    s和p的前缀都是空串时,匹配.

具体实现:

classSolution{public:boolisMatch(string s, string p){int n=s.size(),m=p.size(); vector<vector<bool>>dp(n+1,vector<bool>(m+1));//j=0时p的空串 dp[0][0]=true;for(int j=0;j<m;++j){if(p[j]!='*')break; dp[0][j+1]=true;}for(int i=1;i<=n;++i){for(int j=1;j<=m;++j){if(p[j-1]=='?')dp[i][j]=dp[i-1][j-1];elseif(p[j-1]=='*')dp[i][j]=dp[i-1][j]||dp[i][j-1];elseif(s[i-1]==p[j-1])dp[i][j]=dp[i-1][j-1];//else false}}return dp[n][m];}};

时间复杂度O(mn),空间复杂度O(mn)

同样的我们只依赖属于上一行的元素,因此可以进行空间优化:

classSolution{public:boolisMatch(string s, string p){int m=s.size(),n=p.size();if(!m&&!n)returntrue;bool cur,prev; vector<bool>dp(n+1);//j=0时p的空串 prev=true;for(int j=0;j<n;++j){if(p[j]!='*')break; dp[j+1]=true;}for(int i=1;i<=m;++i){if(i>1)prev=false;for(int j=1;j<=n;++j){ cur=dp[j];if(p[j-1]=='?')dp[j]=prev;elseif(p[j-1]=='*')dp[j]=cur||dp[j-1];elseif(s[i-1]==p[j-1])dp[j]=prev;else dp[j]=false; prev=cur;}}return dp[n];}};

时间复杂度O(mn),空间复杂度O(n)

正则表达式匹配

题目描述

正则表达式匹配
给你一个字符串 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 <= s.length <= 20
  • 1 <= p.length <= 20
  • s 只包含从 a-z 的小写字母。
  • p 只包含从 a-z 的小写字母,以及字符 . 和 *。
  • 保证每次出现字符 * 时,前面都匹配到有效的字符

算法原理和实现

这题和上一题本质没有区别.无非就是’*‘要和前一个字符联合使用,并且’*‘不再是随意匹配,所以模式串末尾是’*'分两种情况:

在这里插入图片描述


注意匹配多个字符的情况下,模式串的倒数第二个元素要和原串最后一个元素匹配,否则就是false.

最终状态表示:
dp[i][j]:s以i结尾的前缀和p以j为结尾的前缀能否匹配.

注意这里初始化和上一题类似,但是我们的’*'是要间隔才能匹配空串.

具体实现:

classSolution{public:boolisMatch(string s, string p){int m=s.size(),n=p.size(); vector<vector<bool>>dp(m+1,vector<bool>(n+1)); dp[0][0]=true;for(int j=2;j<=n;j+=2){if(p[j-1]!='*')break; dp[0][j]=true;}for(int i=1;i<=m;++i){for(int j=1;j<=n;++j){if(s[i-1]==p[j-1]||p[j-1]=='.')dp[i][j]=dp[i-1][j-1];elseif(p[j-1]=='*'){ dp[i][j]=dp[i][j-2];//丢弃if(!dp[i][j-2]&&(s[i-1]==p[j-2]||p[j-2]=='.')) dp[i][j]=dp[i-1][j];}}}return dp[m][n];}};

时间复杂度和空间复杂度都是O(mn)

同样的我们可以进行空间优化:

classSolution{public:boolisMatch(string s, string p){int m=s.size(),n=p.size();bool cur,prev; vector<bool>dp(n+1); prev=true;for(int j=2;j<=n;j+=2){if(p[j-1]!='*')break; dp[j]=true;}for(int i=1;i<=m;++i){if(i>1)prev=false;//s不空,p空,falsefor(int j=1;j<=n;++j){ cur=dp[j];if(s[i-1]==p[j-1]||p[j-1]=='.')dp[j]=prev;elseif(p[j-1]=='*'){ dp[j]=dp[j-2];//丢弃if(!dp[j]&&(s[i-1]==p[j-2]||p[j-2]=='.'))//匹配多个 dp[j]=cur;}else{ dp[j]=false;} prev=cur;}}return dp[n];}};

时间复杂度O(mn),空间复杂度O(n).

交错字符串

题目描述

交错字符串
给定三个字符串 s1、s2、s3,请你帮忙验证 s3 是否是由 s1 和 s2 交错 组成的。

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

s = s1 + s2 + … + sn
t = t1 + t2 + … + tm
|n - m| <= 1
交错 是 s1 + t1 + s2 + t2 + s3 + t3 + … 或者 t1 + s1 + t2 + s2 + t3 + s3 + …
注意:a + b 意味着字符串 a 和 b 连接。

示例 1:

输入:s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbcbcac”
输出:true

示例 2:

输入:s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbbaccc”
输出:false

示例 3:

输入:s1 = “”, s2 = “”, s3 = “”
输出:true

提示:

  • 0 <= s1.length, s2.length <= 100
  • 0 <= s3.length <= 200
  • s1、s2、和 s3 都由小写英文字母组成
  • 进阶:您能否仅使用 O(s2.length) 额外的内存空间来解决它?

算法原理和实现

实际上只要s1和s2的子序列能拼接成s3,那么都是交错拼接.

我们考虑状态表示为:
dp[i][j]为s1以i为结尾的前缀和s2以j为结尾的前缀能否交错拼接成s3以i+j+1为结尾的前缀.

那么分为四种情况:

  1. s1[i]==s2[j]==s3[i+j+1]
    那么拼接的交错序列既有可能以s1[i]结尾,也有可能以s2[j]结尾
  2. s1[i]==s3[i+j+1]
    那么一定以s1[i]结尾
  3. s2[j]==s3[i+j+1]
    那么一定以s2[j]结尾
  4. 其他情况,一定不能拼接.

此外我们需要注意初始化,如果我们给dp表多申请一行一列.那么第一行就是s1前缀为空的时候,s2的前缀必须和s3的前缀完全相等才是true,只要出现一个不相等的字符后续都是false.
第一列也是同理.

具体实现:

classSolution{public:boolisInterleave(string s1, string s2, string s3){int len1=s1.size(),len2=s2.size(),len3=s3.size();if(len1+len2!=len3)returnfalse; vector<vector<bool>>dp(len1+1,vector<bool>(len2+1)); dp[0][0]=true;for(int i=1;i<=len2;++i){if(s2[i-1]!=s3[i-1])break; dp[0][i]=true;}for(int i=1;i<=len1;++i){if(s1[i-1]!=s3[i-1])break; dp[i][0]=true;}for(int i=1;i<=len1;++i){for(int j=1;j<=len2;++j){if(s1[i-1]==s2[j-1]&&s1[i-1]==s3[i+j-1]){ dp[i][j]=dp[i][j-1]||dp[i-1][j];}elseif(s1[i-1]==s3[i+j-1]){ dp[i][j]=dp[i-1][j];}elseif(s2[j-1]==s3[i+j-1]){ dp[i][j]=dp[i][j-1];}}}return dp[len1][len2];}};

时间复杂度和空间复杂度都是O(mn).

同样的我们只借助上一行的状态,因此可以进行空间优化:

classSolution{public:boolisInterleave(string s1, string s2, string s3){int m=s1.size(),n=s2.size();if(m+n!=s3.size())returnfalse;if(m<n){swap(m,n);swap(s1,s2);}if(!n)return s1==s3; vector<bool>dp(n+1); dp[0]=true;//s1为空时for(int i=1;i<=n;++i){if(s2[i-1]!=s3[i-1])break; dp[i]=true;}for(int i=1;i<=m;++i){//比较s2为空时if(s1[i-1]!=s3[i-1])dp[0]=false;for(int j=1;j<=n;++j){if(s1[i-1]==s2[j-1]&&s1[i-1]==s3[i+j-1]){ dp[j]=dp[j-1]||dp[j];//说明有可能取到dp[0]}elseif(s1[i-1]==s3[i+j-1]){continue;}elseif(s2[j-1]==s3[i+j-1]){ dp[j]=dp[j-1];}else{ dp[j]=false;}}}return dp[n];}};

时间复杂度O(mn),空间复杂度O(min(m,n)).

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

题目描述

两个字符串的最小ASCII删除和
给定两个字符串s1 和 s2,返回 使两个字符串相等所需删除字符的 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 的结果,比答案更大。

提示:

  • 1 <= s1.length, s2.length <= 1000
  • s1 和 s2 由小写英文字母组成

算法原理和实现

根据经验,我们定义dp[i][j]为使得s1以i为结尾的前缀和s2以j为结尾的前缀要删除的最小ascll码值.

那么当s1[i]==s2[j]时,势必不用删除最后一个元素,dp[i][j]=dp[i-1][j-1].
当s1[i]!=s2[j]时,势必要删掉s1[i]或者s2[j]中的一个,我们选取删掉一个情况的最小值:dp[i][j]=min(dp[i-1][j]+s1[i],dp[i][j-1]+s2[j])

此外要注意初始化.如果我们给dp表多开一行一列,那么第一行就是完全删除s2[j]的前缀,第一列就是完全删除s1[i]的前缀.

具体实现:

classSolution{public:intminimumDeleteSum(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<=n;++i)dp[0][i]=dp[0][i-1]+s2[i-1];for(int i=1;i<=m;++i)dp[i][0]=dp[i-1][0]+s1[i-1];for(int i=1;i<=m;++i){for(int j=1;j<=n;++j){if(s1[i-1]==s2[j-1])dp[i][j]=dp[i-1][j-1];else dp[i][j]=min(dp[i-1][j]+s1[i-1],dp[i][j-1]+s2[j-1]);}}return dp[m][n];}};

时间复杂度和空间复杂度都是O(mn).

显然我们每一次更新都只需要借助上一行的状态,我们可以进行状态压缩:

classSolution{public:intminimumDeleteSum(string s1, string s2){int m=s1.size(),n=s2.size(),cur,prev;if(m<n){swap(m,n);swap(s1,s2);} vector<int>dp(n+1);for(int i=1;i<=n;++i)dp[i]=dp[i-1]+s2[i-1];for(int i=1;i<=m;++i){ prev=dp[0]; dp[0]+=s1[i-1];for(int j=1;j<=n;++j){ cur=dp[j];if(s1[i-1]==s2[j-1])dp[j]=prev;else dp[j]=min(cur+s1[i-1],dp[j-1]+s2[j-1]); prev=cur;}}return dp[n];}};

时间复杂度O(mn),空间复杂度O(min(m,n))

最长重复子数组

题目描述

最长重复子数组
给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。

示例 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

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 100

算法原理和实现

根据经验,我们定义状态表示为:
dp[i][j]为nums1中以i为结尾的所有子数组和nums2中以j为结尾的左右子数组的最大公共子数组.

显然当nums1[i]!=nums2[j]时,dp[i][j]=0.
否则就能拼接上dp[i-1][j-1]
具体实现:

classSolution{public:intfindLength(vector<int>& nums1, vector<int>& nums2){int m=nums1.size(),n=nums2.size(),ret=0; 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]=0;else dp[i][j]=dp[i-1][j-1]+1; ret=max(ret,dp[i][j]);}}return ret;}};

时间复杂度和空间复杂度都是O(mn)

进一步空间优化:

classSolution{public:intfindLength(vector<int>& nums1, vector<int>& nums2){int m=nums1.size(),n=nums2.size(),ret=0,prev,cur;if(m<n){swap(m,n);swap(nums1,nums2);} vector<int>dp(n+1);for(int i=0;i<m;++i){ prev=0;for(int j=0;j<n;++j){ cur=dp[j];if(nums1[i]!=nums2[j])dp[j]=0;else dp[j]=prev+1; ret=max(ret,dp[j]); prev=cur;}}return ret;}};

时间复杂度O(mn),空间复杂度O(min(m,n)).

Read more

个性化图书推荐系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】

个性化图书推荐系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】

摘要 随着数字化阅读的普及,个性化图书推荐系统在提升用户体验和满足读者需求方面发挥了重要作用。传统的图书推荐方式往往基于简单的分类或热门榜单,难以满足读者多样化的兴趣偏好。现代推荐系统通过分析用户行为数据、阅读历史和偏好,能够提供更加精准的个性化推荐。本研究旨在开发一个基于SpringBoot后端、Vue前端和MySQL数据库的个性化图书推荐系统,该系统能够通过算法分析用户行为,动态调整推荐内容,从而提升用户的阅读体验和满意度。关键词:个性化推荐、数字化阅读、用户行为分析、动态调整、阅读体验。 本研究采用SpringBoot作为后端框架,结合Vue.js前端技术,构建了一个高效、可扩展的个性化图书推荐系统。系统通过MySQL数据库存储用户数据、图书信息和推荐记录,并利用协同过滤算法和内容-based算法实现精准推荐。功能模块包括用户注册与登录、图书浏览与搜索、推荐列表生成、用户反馈收集等。系统支持管理员对图书信息进行管理,同时提供用户个人中心,方便查看阅读历史和推荐记录。后端采用RESTful API设计,前端通过Axios实现数据交互,确保系统的高效运行和良好的用户体验。关键词:

By Ne0inhk
Springboot 4.0十字路口:虚拟线程时代,WebFlux与WebMVC的终极选择

Springboot 4.0十字路口:虚拟线程时代,WebFlux与WebMVC的终极选择

🧑 博主简介:ZEEKLOG博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可关注公众号 “ 心海云图 ” 微信小程序搜索“历代文学”)总架构师,16年工作经验,精通Java编程,高并发设计,分布式系统架构设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。 🤝商务合作:请搜索或扫码关注微信公众号 “ 心海云图 ” Springboot 4.0十字路口:虚拟线程时代,WebFlux与WebMVC的终极选择 当虚拟线程以革命性的姿态降临Java世界,一场关于并发编程范式的静默变革正在发生。Spring开发者站在了选择的十字路口。 2023年,Java 21将虚拟线程从预览特性转为正式功能,这一变化看似只是JVM内部的优化,实则撼动了整个

By Ne0inhk

Fish Speech-1.5多语种语音合成实战:中英混合文本发音规则处理技巧

Fish Speech-1.5多语种语音合成实战:中英混合文本发音规则处理技巧 1. 引言 语音合成技术正在改变我们与数字内容互动的方式,而多语种混合文本的合成更是其中的技术难点。想象一下,当你需要制作一段同时包含中文和英文的教学音频,或者一段中英混合的产品介绍时,传统的单语种语音合成往往会出现发音不自然、语调突兀的问题。 Fish Speech V1.5作为基于超过100万小时多语言音频数据训练的先进文本转语音模型,特别擅长处理这类混合语言场景。本文将带你从零开始,通过xinference 2.0.0部署Fish Speech-1.5,并重点分享中英混合文本的发音处理技巧,让你能够生成自然流畅的多语言语音内容。 2. Fish Speech-1.5模型概述 2.1 模型特点与优势 Fish Speech V1.5是一个功能强大的多语言文本转语音模型,其核心优势在于支持12种主要语言的高质量语音合成。该模型基于海量音频数据训练,其中中文和英语各超过30万小时,日语超过10万小时,其他语言如德语、法语、西班牙语等也都有充足的训练数据。 这种大规模多语言训练使得模型在处理

By Ne0inhk
他到底喜欢我吗?赛博塔罗Java+前端实现,一键解答!

他到底喜欢我吗?赛博塔罗Java+前端实现,一键解答!

个人主页-爱因斯晨 文章专栏-赛博算命 原来我们在已往的赛博算命系列文章中的源码已经传到我的Github仓库中,有兴趣的家人们可以自己运行查看。 Github 源码中的一些不足,还恳请业界大佬们批评指正! 本文章的源码已经打包至资源绑定,仓库中也同步更新。 一、引言 在数字化浪潮席卷全球的当下,传统塔罗牌占卜这一古老智慧也迎来了新的表达形式 ——“赛博塔罗”。本文档旨在深入剖析塔罗牌的核心原理,并详细介绍如何利用 Java 语言实现一个简易的塔罗牌预测程序,展现传统神秘学与现代编程技术的融合。 二、塔罗牌原理 (一)集体潜意识与原型理论 瑞士心理学家卡尔・荣格提出的 “集体潜意识” 理论,为塔罗牌的运作提供了重要的心理学支撑。该理论认为,人类拥有超越个体经验的共同心理结构,其中蕴含着 “原型”—— 即普遍存在的、象征性的模式或形象。 塔罗牌的 22 张大阿尔卡那牌恰好与这些基本原型相对应。例如,“愚人” 代表着天真与新开始的原型,“魔术师” 象征着创造力与潜能的原型,“女祭司” 则体现了智慧与直觉的原型。这些原型是全人类共通的心理元素,这也正是不同文化背景的人都能

By Ne0inhk