最长递增子序列
- 力扣题目链接
- 子序列是可以删除元素但不改变元素相对顺序的一种序列
- 非常适合使用dp来解决问题
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
int result=1;
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;
}
最长连续递增序列
- 力扣题目链接
- dp简单题,只需要一重循环
int findLengthOfLCIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
int result=1;
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;
}
最长重复子数组
- 力扣题目链接
- 明确
dp[i][j]的含义,以i-1和j-1结尾的最长重复子数组 - 注意这里的子序列是连续的!
int findLength(vector<int>& nums1, vector<int>& nums2) {
// dp[i][j]的含义是以i-1和j-1结尾的数组的最长重复子数组长度
vector<vector<int>> dp(nums1.size()+1,
vector<int>(nums2.size()+1, 0));
// 用result是因为dp.back()不一定是最长的,可能在中间取得
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;
}
最长公共子序列
- 力扣题目链接
- 这道题在后面会重复多次,熟记模板
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()];
}
不相交的线
- 力扣题目链接
- 本质上就是最长公共子序列!!
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()];
}
最大子序和
- 力扣题目链接
- 这道题曾经在贪心做过,我们这里用dp再做一遍!
int maxSubArray(vector<int>& nums) {
if (nums.size() == 0) return 0;
vector<int> dp(nums.size(), 0); // dp[i]表示包括i前的最大连续子序列和
dp[0] = nums[0];
int result=dp[0];
for(int i=1; i<nums.size(); i++){
dp[i]=max(dp[i-1]+nums[i], nums[i]);
if(dp[i]>result) result=dp[i];
}
return result;
}
判断子序列
- 力扣题目链接
- 和最长公共子序列相比,不能删除s的元素
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;
// 相当于t要删除元素t[j-1],注意这里的区别
else dp[i][j]=dp[i][j-1];
if(dp[s.size()][t.size()]==s.size()) return true;
else return false;
}
- 当然,双指针法也秒了:
bool isSubsequence(string s, string t) {
if(s.empty()) return true;
int i=0, j=0;
while(j<t.size()){
if(s[i]!=t[j]) j++;
else i++, j++;
if(i==s.size()) return true;
}
return false;
}
不同的子序列
- 力扣题目链接
- 注意这里处理字符相等时的不同,加上删除s的字符后出现的次数
- 注意初始化的不同
int numDistinct(string s, string t) {
vector<vector<uint64_t>> dp(s.size()+1,
vector<uint64_t>(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];
// 模拟在s中删除这个元素
else dp[i][j]=dp[i-1][j];
return dp[s.size()][t.size()];
}
两个字符串的删除操作
- 力扣题目链接
- 实际上就是找最长公共子序列,总长度减即可!
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()];
}
编辑距离
- 力扣题目链接
- 上面所有的题目都是在为这道题目铺垫
- 字符相同无需操作,不相同,增删改里面找最小的!
- 特别需要注意初始化的重大区别!决定本题的关键!!
int minDistance(string word1, string word2) {
int m=word1.size()+1, n=word2.size()+1;
vector<vector<int>> dp(m, vector<int>(n));
for(int i=0; i<m; i++) dp[i][0]=i;
for(int j=0; j<n; j++) dp[0][j]=j;
for(int i=1; i<m; i++)
for(int j=1; j<n; j++)
if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1];
else dp[i][j]=min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]})+1;
return dp[m-1][n-1];
}
回文子串
- 力扣题目链接
- 区间dp的题目,需要仔细揣摩,
dp[i][j]代表左闭右闭的区间
int countSubstrings(string s) {
int n=s.size();
vector<vector<bool>> dp(n, vector<bool>(n, false));
int result=0;
// 注意遍历顺序
for(int i=n-1; i>=0; i--)
for(int j=i; j<n; j++)
if(s[i]==s[j])
if(j-i<=1 || dp[i+1][j-1]) {
result++; dp[i][j]=true;
}
return result;
}
最长回文子序列
- 力扣题目链接
- 这道题不是计数的了,可以删除元素
int longestPalindromeSubseq(string s) {
int n=s.size();
vector<vector<int>> dp(n, vector<int>(n, 0));
for(int i=0; i<n; i++) dp[i][i]=1;
for(int i=n-1; i>=0; i--)
for(int j=i+1; j<n; 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][n-1];
}
二叉树中的最大路径和
- 力扣题目链接
- 贴一道树形dp的题目,其实dp无所谓什么形式,只是对dp数组解读不同罢了
int result=INT_MIN;
int dfs(TreeNode* root){
if(root==nullptr) return 0;
int left_max=max(dfs(root->left), 0);
int right_max=max(dfs(root->right), 0);
// 包含当前节点和左右子树的最大路径和
int cur_max=left_max+right_max+root->val;
result=max(cur_max, result);
// 返回经过root的单边最大分支给当前root的父节点计算使用
return max(left_max, right_max)+root->val;
}
int maxPathSum(TreeNode* root) {
dfs(root);
return result;
}
总结
- 动态规划入门不难,但后面真的是难难难!想有多难可以有多难
- 更新比较仓促,主要为考研复试做准备,更完这期就去参加复试了,加油!
参考资料
[1] 代码随想录
[2] Leetcode题解
[3] Leetcode算法通关手册