300.最长递增子序列 难点解析: 1.明确dp数组的具体意义,包括下标和存储内容,这里的下标指遍历到的数组下标,存储内容为以该下标为结尾的最长子序列长度 2.确定递推迭代公式,注意,这里是需要通过比较取较大值的 3.在充分理解1的情况下,设计result记录最长递增子序列。 4.注意j的迭代循环范围是不能超过i的
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
//采用动态规划方式进行递推迭代
//剪支
if(nums.size()==0) return 0;
//设计dp数组,并初始化,这里注意,数组元素均应当初始化为1;
vector<int> dp(nums.size(),1);//下标i为遍历到的数组长度,内容存储子序列长度
//以 nums [i] 为最后一个元素的最长递增子序列长度(这是 LIS 动态规划的核心定义);
int result=1;
//进行双循环迭代,外层循环为整数数组,内层循环为子序列长度i
for(int i=1;i<nums.size();i++){
for(int j=0;j<i;j++){//注意这里只能遍历到i-1!!!
if(nums[j]<nums[i]) dp[i]=max(dp[i],dp[j]+1);
}
//存储最大子序列长度
if(dp[i]>result) result=dp[i];
}
return result;
}
};
674.最长连续递增序列 采用贪心算法最快
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
int result=1;
int tmp=1;
for(int i=1;i<nums.size();i++){
if(nums[i]>nums[i-1]){
tmp++;
if(tmp>result) result=tmp;
}
else
tmp=1;
}
return result;
}
};
下面展示采用动态规划算法
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
int result=1;
//设计dp数组并初始化,均初始化为1
vector<int> dp(nums.size(),1);
//执行递推公式
for(int i=1;i<nums.size();i++){
if(nums[i]>nums[i-1]) dp[i]=dp[i-1]+1;
if(result<dp[i]) result=dp[i];
}
return result;
}
};
718.最长重复子数组 难点解析; 1.如何设计dp数组,注意这里有两个基础对比条件,分别是A,B数组,建议设计二维数组方便理解,同时注意为了简便运算,应当将存储内容设计为包括i-1,j-1的最长数组长度 2.边界问题,因为我们设计存储内容为i-1,j-1,所以我们开始循环应当从1开始,并且我们结束应当为nums1.size()和nums2.size(),所以我们的循环结束边界应当为nusm1.size();
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
//采用动态规划得出最长重复子数组
//排除意外情况或者剪支处理
if(nums1.size()==0||nums2.size()==0) return 0;
//设计dp数组,考虑到为两个整数数组,需要两个下标表示具体含义,内容存储为i-1,j-1时的最长子序列长度
vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
//已经初始化为0;设计result存储结果
int result=0;
//双层循环遍历
for(int i=1;i<=nums1.size();i++){//注意这里的边界必须到nums1.size(),因为我们设计的时nums1.size()规模的数组
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(result<dp[i][j]) result=dp[i][j];
}
}
return result;
}
};
滚动数组版本,注意,滚动数组的设计包括两个下标具备相同的变化时可以应用
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
vector<int> dp(vector<int>(B.size() + 1, 0));
int result = 0;
for (int i = 1; i <= A.size(); i++) {
for (int j = B.size(); j > 0; j--) {
if (A[i - 1] == B[j - 1]) {
dp[j] = dp[j - 1] + 1;
} else dp[j] = 0; // 注意这里不相等的时候要有赋0的操作
if (dp[j] > result) result = dp[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]相同的话,同理。
1143.最长公共子序列,区别在于不相同时增加了不同取值判断
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
//采用动态规划
//设计dp数组,并初始化为0,为了方便后序编写代码,dp[i][j]数组存储的时i-1,j-1长度的公共子序列
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][j-1],dp[i-1][j]);//取较大值
}
}
return dp[text1.size()][text2.size()];
}
};
1035.不相交的线 这里的难点在于理解题意:不相交的情况应当满足i,j之间的相对顺序不变,因此可以当作是求最大公共子序列长度
class Solution {
public:
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.最大子序和 贪心算法
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//贪心算法
int result = INT32_MIN;
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums[i];
if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
result = count;
}
if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
}
return result;
}
};
动态规划算法
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//动态规划算法
vector<int> dp(nums.size(),0);
//初始化
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(result<dp[i]) result=dp[i];
}
return result;
}
};
392.判断子序列,与最长公共子序列没有什么特别大区别,甚至更加简单
class Solution {
public:
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];
}
}
if (dp[s.size()][t.size()] == s.size()) return true;
return false;
}
};
115.不同的子序列 1.本题难点在于求子序列的个数,因此如何设计状态转移递推公式最重要 2.初始化必须慎重考虑。每次当初始化的时候,都要回顾一下dp[i][j]的定义,不要凭感觉初始化。
dp[i][0]表示什么呢?
dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
再来看dp[0][j],dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。
那么dp[0][j]一定都是0,s如论如何也变成不了t。
最后就要看一个特殊位置了,即:dp[0][0] 应该是多少。
dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。
class Solution {
public:
int numDistinct(string s, string t) {
//采用动态规划的方式进行迭代,同时,题设使用的是字符,所以使用vector的时候应当使用unint64_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];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[s.size()][t.size()];
}
};