300.最长递增子序列
int lengthOfLIS(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
vector<int> dp(nums.size(),1);
int result=0;
for(int i=1;i<nums.size();i++){
for(int j=0;j<i;j++){
if(nums[i]>nums[j]) dp[i]=max(dp[i],dp[j]+1);
}
if(dp[i]>result) result=dp[i];// 取长的子序列
}
return result;
}
674. 最长连续递增序列
int findLengthOfLCIS(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
vector<int> dp(nums.size(),1);
int result=0;
for(int i=1;i<nums.size();i++){
if(nums[i]>nums[i-1]) // 连续记录
dp[i]=dp[i-1]+1;
if(dp[i]>result)
result=dp[i];// 取长的子序列
}
return result;
}
718. 最长重复子数组
1.确定dp数组(dp table)以及下标的含义
dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。 (特别注意: “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串 )
2.确定递推公式
根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。
即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
根据递推公式可以看出,遍历i 和 j 要从1开始!
3.dp数组如何初始化
根据dp[i][j]的定义,dp[i][0] 和dp[0][j]其实都是没有意义的!
但dp[i][0] 和dp[0][j]要初始值,因为 为了方便递归公式dp[i][j] = dp[i - 1][j - 1] + 1;
所以dp[i][0] 和dp[0][j]初始化为0。
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
int result=0;
for(int i=1;i<=nums1.size();i++){
for(int j=1;j<=nums2.size();j++){
if(nums1[i-1]==nums2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
if(dp[i][j]>result)
result=dp[i][j];// 取长的子序列
}
}
return result;
}
拓展
前面讲了 dp数组为什么定义:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。
我就定义dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,最长重复子数组长度。不行么?
当然可以,就是实现起来麻烦一些。
如果定义 dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,那么 第一行和第一列毕竟要进行初始化,如果nums1[i] 与 nums2[0] 相同的话,对应的 dp[i][0]就要初始为1, 因为此时最长重复子数组为1。 nums2[j] 与 nums1[0]相同的话,同理。
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
// 要对第一行,第一列经行初始化
for (int i = 0; i < nums1.size(); i++) if (nums1[i] == nums2[0]) dp[i][0] = 1;
for (int j = 0; j < nums2.size(); j++) if (nums1[0] == nums2[j]) dp[0][j] = 1;
for (int i = 0; i < nums1.size(); i++) {
for (int j = 0; j < nums2.size(); j++) {
if (nums1[i] == nums2[j] && i > 0 && j > 0) { // 防止 i-1 出现负数
dp[i][j] = dp[i - 1][j - 1] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
在推导出转移方程后可能疑惑上述代码为什么要从i=0,j=0遍历而不是从i=1,j=1开始遍历,原因在于这里如果不是从i=0,j=0位置开始遍历,会漏掉如下样例结果:
nums1 = [70,39,25,40,7]
nums2 = [52,20,67,5,31]
也就是说,一开始都是初始化为0,假如最长地址子序列就是1的话,会漏掉result和dp[0][j] dp[i][0]的比较。
1143.最长公共子序列
本题和动态规划:718. 最长重复子数组 区别在于这里不要求是连续的了,但要有相对顺序,即:“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
int longestCommonSubsequence(string text1, string text2) {
vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));
for(int i=1;i<=text1.size();i++){
for(int j=1;j<=text2.size();j++){
if(text1[i-1]==text2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[text1.size()][text2.size()];
}
1035.不相交的线
本题就和动态规划:1143.最长公共子序列 (opens new window)是一样的。
int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
for(int i=1;i<=nums1.size();i++){
for(int j=1;j<=nums2.size();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]);
}
}
return dp[nums1.size()][nums2.size()];
}
53. 最大子序和
nt maxSubArray(vector<int>& nums) {
if(nums.size()==0) return 0;
vector<int> dp(nums.size());
dp[0]=nums[0];
int result=dp[0];///不是0
for(int i=1;i<nums.size();i++){
dp[i]=max(dp[i-1]+nums[i],nums[i]);
result = result > dp[i] ? result : dp[i];
}
return result;
}
392.判断子序列
这道题目算是编辑距离的入门题目
dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。
确定递推公式
在确定递推公式的时候,首先要考虑如下两种操作,整理如下:
if (s[i - 1] == t[j - 1])
t中找到了一个字符在s中也出现了
if (s[i - 1] != t[j - 1])
相当于t要删除元素,继续匹配
if (s[i - 1] == t[j - 1]),那么dp[i][j] = dp[i - 1][j - 1] + 1;,因为找到了一个相同的字符,相同子序列长度自然要在dp[i-1][j-1]的基础上加1(如果不理解,在回看一下dp[i][j]的定义)
if (s[i - 1] != t[j - 1]),此时相当于t要删除元素,t如果把当前元素t[j - 1]删除,那么dp[i][j] 的数值就是 看s[i - 1]与 t[j - 2]的比较结果了,即:dp[i][j] = dp[i][j - 1];
其实这里 和 1143.最长公共子序列 (opens new window)的递推公式基本那就是一样的,区别就是 本题 如果删元素一定是字符串t,而 1143.最长公共子序列 是两个字符串都可以删元素。
dp数组如何初始化
从递推公式可以看出dp[i][j]都是依赖于dp[i - 1][j - 1] 和 dp[i][j - 1],所以dp[0][0]和dp[i][0]是一定要初始化的。
bool isSubsequence(string s, string t) {
vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1,0));
for(int i=1;i<=s.size();i++){
for(int j=1;j<=t.size();j++){
if(s[i-1]==t[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=dp[i][j-1];
}
}
return dp[s.size()][t.size()]==s.size();
}
115.不同的子序列
int numDistinct(string s, string t) {
//vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1));
vector<vector<uint64_t>> dp(s.size()+1,vector<uint64_t>(t.size()+1));
//vector<vector<long long>> dp(s.size() + 1, vector<long long>(t.size() + 1));
for(int i=0;i<=s.size();i++){
dp[i][0]=1;
}
for(int j=1;j<=t.size();j++){
dp[0][j]=0;
}
for(int i=1;i<=s.size();i++){
for(int j=1;j<=t.size();j++){
if(s[i-1]==t[j-1])
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
else
dp[i][j]=dp[i-1][j];
}
}
return dp[s.size()][t.size()];
}
583. 两个字符串的删除操作
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1));
for(int i=0;i<=word1.size();i++) dp[i][0]=i;
for(int j=0;j<=word2.size();j++) dp[0][j]=j;
for(int i=1;i<=word1.size();i++){
for(int j=1;j<=word2.size();j++){
if(word1[i-1]==word2[j-1])
dp[i][j]=dp[i-1][j-1];
else
dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+2);
}
}
return dp[word1.size()][word2.size()];
}
思路2:
多余元素个数(删除操作)=word1.size()+ word2.size() -2*最长公共子序列
同1143.最长公共子序列
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));
for(int i=1;i<=word1.size();i++){
for(int j=1;j<=word2.size();j++){
if(word1[i-1]==word2[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
return word1.size()+ word2.size() -2*dp[word1.size()][word2.size()];
}
72. 编辑距离
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));
for(int i=0;i<=word1.size();i++) dp[i][0]=i;
for(int j=0;j<=word2.size();j++) dp[0][j]=j;
for(int i=1;i<=word1.size();i++){
for(int j=1;j<=word2.size();j++){
if(word1[i-1]==word2[j-1])
dp[i][j]=dp[i-1][j-1];
else
dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+1);
//dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
}
}
return dp[word1.size()][word2.size()];
}
647. 回文子串
int countSubstrings(string s) {
vector<vector<bool>> dp(s.size()+1,vector<bool>(s.size()+1,false));
int result=0;
for(int i=s.size()-1;i>=0;i--){ 注意遍历顺序
for(int j=i;j<s.size();j++){
if(s[i]==s[j]){
if(j-i<=1){// 情况一 和 情况二
dp[i][j]=true;
result++;
}else if(dp[i+1][j-1] == true){// 情况三
dp[i][j]=true;
result++;
}
}
}
}
return result;
}
双指针法
动态规划的空间复杂度是偏高的,我们再看一下双指针法。
首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。
在遍历中心点的时候,要注意中心点有两种情况。
一个元素可以作为中心点,两个元素也可以作为中心点。
那么有人同学问了,三个元素还可以做中心点呢。其实三个元素就可以由一个元素左右添加元素得到,四个元素则可以由两个元素左右添加元素得到.
所以我们在计算的时候,要注意一个元素为中心点和两个元素为中心点的情况。
这两种情况可以放在一起计算,但分别计算思路更清晰,我倾向于分别计算,代码如下:
int countSubstrings(string s) {
int result = 0;
for (int i = 0; i < s.size(); i++) {
result += extend(s, i, i, s.size()); // 以i为中心
result += extend(s, i, i + 1, s.size()); // 以i和i+1为中心
}
return result;
}
int extend(const string& s, int i, int j, int n) {
int res = 0;
while (i >= 0 && j < n && s[i] == s[j]) {
i--;
j++;
res++;
}
return res;
}
516.最长回文子序列
int longestPalindromeSubseq(string s) {
vector<vector<int>> dp(s.size(),vector<int>(s.size(),0));
for(int i=0; i<s.size();i++) dp[i][i]=1;
for(int i=s.size()-1;i>=0;i--){ 注意遍历顺序
for(int j=i+1;j<s.size();j++){
if(s[i]==s[j]){
dp[i][j]=dp[i+1][j-1]+2;
}else{
dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
}
}
}
return dp[0][s.size()-1];
}