LeetCode300. 最长递增子序列
https://leetcode-cn.com/problems/longest-increasing-subsequence/
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
思路
1.思考状态:找出最优解的性质,明确状态表示什么
dp[i]表示数组nums[0 … i]的最长递增子序列的长度。
2.思考状态转移方程:大问题的最有解如何由小问题的最优解得到
位置i的最长递增子序列等于j从0到i-1各个位置的最长递增子序列 + 1 的最大值。
所以:if (nums[i] > nums[j]): dp[i] = max(dp[j] + 1),其中0 <= j <= i - 1
3.思考初始状态
每一个位置i,对应的dp[i](即最长递增子序列)起始大小至少都是是1.
所以:dp[i] = 1
4.自底向上计算得到最优解
dp[i] 是有0到i-1各个位置的最长递增子序列推导而来,那么遍历i一定是从前向后遍历。
即:
int result = 1;
for (int i = 1; i < size; ++i)
{
for (int j = 0; j < i; ++j)
{
if (nums[i] > nums[j])
{
dp[i] = max(dp[i], dp[j] + 1);
}
}
result = max(result, dp[i]);
}
5.思考是否可以进行空间的优化
代码
class Solution
{
public:
int lengthOfLIS(vector<int>& nums)
{
int size = nums.size();
if (size == 0) return 0;
vector<int> dp(size, 1);
int result = 1;
for (int i = 1; i < size; ++i)
{
for (int j = 0; j < i; ++j)
{
if (nums[i] > nums[j])
{
dp[i] = max(dp[i], dp[j] + 1);
}
}
result = max(result, dp[i]);
}
return result;
}
};
LeetCode674. 最长递增子数组
https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/
给定一个未经排序的整数数组,找到最长递增子数组,并返回该序列的长度。
输入:nums = [1,3,5,4,7]
输出:3
解释:最长递增子数组是 [1,3,5], 长度为3。
思路
1.思考状态:找出最优解的性质,明确状态表示什么
以下标i为结尾的数组的连续递增的子序列长度为dp[i]。
注意这里的定义,一定是以下标i为结尾,并不是说一定以下标0为起始位置。
2.思考状态转移方程:大问题的最有解如何由小问题的最优解得到
如果 nums[i] > nums[i - 1],那么以 i 为结尾的数组的连续递增的子序列长度 一定等于 以 i - 1 为结尾的数组的连续递增的子序列长度 + 1 。
所以:if (nums[i] > nums[i - 1]): dp[i] = dp[i - 1] + 1
注意这里就体现出和LeetCode300. 最长递增子序列的区别!
因为本题要求连续递增子序列,所以就必要比较nums[i]与nums[i - 1],而不用去比较nums[i]与nums[j] (j是在0到i - 1之间遍历)。
既然不用j了,那么也不用两层for循环,本题一层for循环就行,比较nums[i] 和 nums[i - 1]。
3.思考初始状态
以下标i为结尾的数组的最递增子数组长度最少也应该是1,即就是nums[i]这一个元素。
所以:dp[i] = 1
4.自底向上计算得到最优解
dp[i + 1]依赖dp[i],所以一定是从前向后遍历。
int result = 1;
for (int i = 1; i < sz; ++i)
{
if (nums[i] > nums[i - 1])
{
dp[i] = dp[i - 1] + 1;
}
result = max(result, dp[i]);
}
5.思考是否可以进行空间的优化
代码
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums)
{
int sz = nums.size();
if (sz == 0) return 0;
vector<int> dp(sz, 1);
int result = 1;
for (int i = 1; i < sz; ++i)
{
if (nums[i] > nums[i - 1])
{
dp[i] = dp[i - 1] + 1;
}
result = max(result, dp[i]);
}
return result;
}
};
LeetCode1143. 最长公共子序列
https://leetcode-cn.com/problems/longest-common-subsequence/
给定两个字符串 text1
和 text2
,返回这两个字符串的最长公共子序列的长度。若这两个字符串没有公共子序列,则返回 0。
输入:text1 = "abcde", text2 = "ace"
输出:3
解释:最长公共子序列是 "ace",它的长度为 3。
思路
1.思考状态:找出最优解的性质,明确状态表示什么
dp[i] [j]表示长度为i的字符串text1和长度为j的字符串text2的最长公共子序列的长度,即[0, i - 1]的字符串text1与[0, j - 1]的字符串text2的最长公共子序列的长度
2.思考状态转移方程:大问题的最有解如何由小问题的最优解得到
主要就是两大情况:text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同
-
如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i] [j] = dp[i - 1] [j - 1] + 1;
-
如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。
即:if (text1[i - 1] == text2[j - 1]): dp[i] [j] = dp[i - 1] [j - 1] + 1
if (text1[i - 1] != text2[j - 1]): dp[i] [j] = max(dp[i - 1] [j], dp[i] [j - 1])
3.思考初始状态
text1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i] [0] = 0;
同理dp[0] [j]也是0。
即:dp[i] [0] = 0
dp[0] [j] = 0
4.自底向上计算得到最优解
for (int i = 1; i <= size1; ++i)
{
for (int j = 1; j <= size2; ++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]);
}
}
}
5.思考是否可以进行空间的优化
代码
class Solution
{
public:
int longestCommonSubsequence(string text1, string text2)
{
int size1 = text1.size();
int size2 = text2.size();
vector<vector<int>> dp(size1 + 1, vector<int>(size2 + 1, 0));
for (int i = 1; i <= size1; ++i)
{
for (int j = 1; j <= size2; ++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[size1][size2];
}
};
LeetCode718. 最长公共子数组
https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/
给两个整数数组 A
和 B
,返回两个数组中公共的、长度最长的子数组的长度。
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:
长度最长的公共子数组是 [3, 2, 1] 。
思路
1.思考状态:找出最优解的性质,明确状态表示什么
dp[i] [j]表示以下标i - 1为结尾的整数数组A与以下标j - 1为结尾的整数数组B的最长公共子数组的长度
2.思考状态转移方程:大问题的最有解如何由小问题的最优解得到
根据dp[i] [j]的定义,dp[i] [j]的状态只能由dp[i - 1] [j - 1]推导出来。
即:if (A[i - 1] == B[i - 1]): dp[i] [j] = dp[i - 1] [j - 1] + 1;
3.思考初始状态
根据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。
举个例子:A[0]如果和B[0]相同的话,dp[1] [1] = dp[0] [0] + 1,只有dp[0] [0]初始为0,正好符合递推公式逐步累加起来。
4.自底向上计算得到最优解
外层for循环遍历A,内层for循环遍历B。
int result = 0;
for (int i = 1; i <= aSize; ++i)
{
for (int j = 1; j <= bSize; ++j)
{
if (A[i - 1] == B[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
result = max(result, dp[i][j]);
}
}
5.思考是否可以进行空间的优化
代码
class Solution
{
public:
int findLength(vector<int>& A, vector<int>& B)
{
int aSize = A.size();
int bSize = B.size();
vector<vector<int>> dp(aSize + 1, vector<int>(bSize + 1, 0));
int result = 0;
for (int i = 1; i <= aSize; ++i)
{
for (int j = 1; j <= bSize; ++j)
{
if (A[i - 1] == B[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
}
result = max(result, dp[i][j]);
}
}
return result;
}
};
LeetCode516. 最长回文子序列
https://leetcode-cn.com/problems/longest-palindromic-subsequence/
给定一个字符串 s
,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s
的最大长度为 1000
。
输入:
"bbbab"
输出:
4
一个可能的最长回文子序列为 "bbbb"。
思路
1.思考状态:找出最优解的性质,明确状态表示什么
dp[i] [j] 表示s[i…j]的最长回文子序列的长度
2.思考状态转移方程:大问题的最有解如何由小问题的最优解得到
关键逻辑就是看s[i]与s[j]是否相同。
-
如果s[i]与s[j]相同,那么dp[i] [j] = dp[i + 1] [j - 1] + 2;
-
如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入不能增加[i,j]区间回文子串的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。
加入s[i]的回文子序列长度为dp[i + 1] [j]。
加入s[j]的回文子序列长度为dp[i] [j - 1]。
那么dp[i] [j]一定是取最大的,即:dp[i] [j] = max(dp[i + 1] [j], dp[i] [j - 1]);
所以: if (s[i] == s[j]): dp[i] [j] = dp[i + 1] [j - 1] + 2
if (s[i] != s[j]): dp[i] [j] = max(dp[i + 1] [j], dp[i] [j - 1])
3.思考初始状态
一个字符的回文子序列长度就是1。
所以:dp[i] [i] = 1
4.自底向上计算得到最优解
从递推公式dp[i] [j] = dp[i + 1] [j - 1] + 2 和 dp[i] [j] = max(dp[i + 1] [j], dp[i] [j - 1]) 可以看出,dp[i] [j]的计算是依赖于dp[i + 1] [j - 1] 、dp[i + 1] [j]和dp[i] [j - 1]
也就是从矩阵的角度来说,dp[i] [j]的计算是依赖与下一行和前一列的数据。 所以遍历i的时候一定要从下到上遍历,遍历j的时候一定要从左到右遍历这样才能保证,下一行的数据,前一列的数据是经过计算的。
for (int i = size - 1; i >= 0; --i)
{
for (int j = i + 1; j < 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]);
}
}
}
5.思考是否可以进行空间的优化
另一种思路
我们可以将字符串逆序得到另一个字符串,将问题转换为求原始字符串和逆序字符串的最长公共子序列,求解最长公共子序列的思路见前文:LeetCode1143. 最长公共子序列。
代码
class Solution
{
public:
//动态规划
int longestPalindromeSubseq(string s)
{
int size = s.size();
if(size == 0)
{
return 0;
}
vector<vector<int>> dp(size, vector<int>(size, 0));
for (int i = 0; i < size; ++i)
{
dp[i][i] = 1;
}
for (int i = size - 1; i >= 0; --i)
{
for (int j = i + 1; j < 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][size - 1];
}
//转换为LCS
/*int longestPalindromeSubseq(string s)
{
int size = s.size();
if(size == 0)
{
return 0;
}
string sReverse(s);
for (int i = 0; i < size; ++i)
{
sReverse[i] = s[size - i - 1];
}
vector<vector<int>> dp(size + 1, vector<int>(size + 1, 0));
for (int i = 1; i <= size; ++i)
{
for (int j = 1; j <= size; ++j)
{
if (s[i - 1] == sReverse[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[size][size];
}*/
};
LeetCode161. 相距为1的编辑距离
https://leetcode-cn.com/problems/one-edit-distance/
给定两个字符串 s 和 t,判断他们的编辑距离是否为 1。
注意:
满足编辑距离等于 1 有三种可能的情形:
- 往 s 中插入一个字符得到 t
- 从 s 中删除一个字符得到 t
- 在 s 中替换一个字符得到 t
输入: s = "ab", t = "acb"
输出: true
解释: 可以将 'c' 插入字符串 s 来得到 t。
输入: s = "cab", t = "ad"
输出: false
解释: 无法通过 1 步操作使 s 变为 t。
思路
字符串s的大小为sSize,字符串t的大小为tSize,不妨假设sSize <= tSize 。
- 如果tSize - sSize >= 2:编辑距离一定不为1
- 如果两个字符串下标为i的字符不相等:
1.如果sSize等于tSize:判断s.substr(i + 1)是否等于t.substr(i + 1)。如果
相等则编辑距离为1(更新操作);如果不相等则编辑距离不为1。
2.如果sSize + 1等于tSize:判断s.substr(i )是否等于t.substr(i + 1)。如果
相等则编辑距离为1(插入操作);如果不相等则编辑距离不为1。
- 如果两个字符串前sSize个字符相等:
1.如果sSize等于tSize:两个字符串相等,编辑距离不为1。
2.如果sSize + 1等于tSize:编辑距离为1(插入操作)
代码
class Solution
{
public:
bool isOneEditDistance(string s, string t)
{
int sSize = s.size();
int tSize = t.size();
if (sSize > tSize)
{
return isOneEditDistance(t, s);
}
if ((tSize - sSize) >= 2)
{
return false;
}
for (int i =0;i < sSize;++i)
{
if (s[i] != t[i])
{
if (sSize == tSize)
{
return s.substr(i + 1) == t.substr(i + 1);
}
else
{
return s.substr(i) == t.substr(i + 1);
}
}
}
return (sSize + 1 == tSize);
}
};
LeetCode72. 编辑距离(LeetCode161. 相距为1的编辑距离进阶)
https://leetcode-cn.com/problems/edit-distance/
给你两个单词 word1
和 word2
,请你计算出将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
思路
1.思考状态:找出最优解的性质,明确状态表示什么
dp[i] [j]表示长度为i的字符串word1和长度为j的字符串word2的编辑距离,即[0, i - 1]的字符串word1变换到[0, j - 1]的字符串word2的最少操作数。
2.思考状态转移方程:大问题的最有解如何由小问题的最优解得到
主要就是两大情况:word1[i - 1] 与word2[j - 1]相同,word1[i - 1] 与word2[j - 1]不相同
- if (word1[i - 1] == word2[j - 1]): dp[i] [j] = dp[i - 1] [j - 1]
因为word1[i - 1]等于word2[j - 1],所以无需进行操作
- if (word1[i - 1] != word2[j - 1]):
dp[i] [j] = 1 + min(dp[i - 1] [j], dp[i] [j - 1], dp[i - 1] [j - 1])
其中,dp[i-1] [j-1] 表示替换操作,dp[i-1] [j] 表示删除操作,dp[i] [j-1]表示插
入操作。
3.思考初始状态
从空串变换到[0, i - 1]的字符串word1的最少操作数为i,所以dp[i] [0] = i
同理,dp[0] [j] = j
4.自底向上计算得到最优解
for (int i = 1;i <= size1;++i)
{
for (int j = 1;j <= size2;++j)
{
if (word1[i - 1] == word2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1];
}
else
{
dp[i][j] = 1 + min(min(dp[i - 1][j],
dp[i][j - 1]), dp[i - 1][j - 1]);
}
}
}
5.思考是否可以进行空间的优化
代码
class Solution
{
public:
int minDistance(string word1, string word2)
{
int size1 = word1.size();
int size2 = word2.size();
int dp[size1 + 1][size2 + 1];
dp[0][0] = 0;
for (int i = 1;i <= size1;++i)
{
dp[i][0] = i;
}
for (int j = 1;j <= size2;++j)
{
dp[0][j] = j;
}
for (int i = 1;i <= size1;++i)
{
for (int j = 1;j <= size2;++j)
{
if (word1[i - 1] == word2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1];
}
else
{
dp[i][j] = 1 + min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]);
}
}
}
return dp[size1][size2];
}
};
word2.size();
int dp[size1 + 1][size2 + 1];
dp[0][0] = 0;
for (int i = 1;i <= size1;++i)
{
dp[i][0] = i;
}
for (int j = 1;j <= size2;++j)
{
dp[0][j] = j;
}
for (int i = 1;i <= size1;++i)
{
for (int j = 1;j <= size2;++j)
{
if (word1[i - 1] == word2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1];
}
else
{
dp[i][j] = 1 + min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]);
}
}
}
return dp[size1][size2];
}
};