这篇是动态规划最后一篇文章,我感觉这类问题难度有点大,重点在子序列的元素在原序列当中是否连续。
子序列不连续问题
最长递增子序列
题目:300. 最长递增子序列 - 力扣(LeetCode)
dp数组及下标含义
dp[i]表示到第i个元素时,最长递增子序列的长度
递推公式
如果dp[j] < dp[i],dp[i] = max(dp[i], dp[j] + 1) j为0到i - 1的数
初始化
全初始化为1,只有一个元素,则子序列长度为1
遍历顺序
从小到大,因为dp[i]的值由下标比它小的值推出
int lengthOfLIS(vector<int> nums){
int result = 0;
vector<int> dp(nums.size(), 1);
for(int i = 1; i < nums.size(); i++)
for(int j = 0; j < i; j++){
if(dp[j] < dp[i])
dp[i] = max(dp[i], dp[j] + 1);
}
if(dp[i] > result)
result = dp[i];
}
return result;
}
上述方法的时间复杂度为O(),在最长上升子序列(二)_牛客题霸_牛客网 (nowcoder.com)就会运行超时。下面介绍一种结合二分查找的动态规划方法。
dp[i]有两个来源:
前面的数都比它大,则dp[i] = 1
找到最长递增子序列中,替换第一个比它大的,可以让更多略大于它的数加入进来(贪心策略)
找出最长递增序列第一个比nums[i]大的下标,就可以使用二分查找
int lengthOfLIS(vector<int> nums){
int result = 1;
vector<int> number(nums.size(), nums[0]);
for(int i = 1; i < nums.size(); i++){
int low = 0, high = result;
while(low < high){
int mid = (low + high) / 2;
if(number[mid] < nums[i])
low = mid + 1;
else
high = mid;
}
if(low == result)
result++;
number[low] = nums[i];
}
return result;
}
最长公共子序列
题目:给定两个字符串text1和text2,返回两个字符串的最长公共子序列的长度。一个字符串的子序列是指:它由原字符串在不改变字符的相对顺序的情况下删除某些字符后组成的新字符串。
示例1:
输入:text1 = "abcde", text2 = "ace"
输出:3
示例2:
输入:text1 = "abc", text2 = "abc"
输出:3
示例3:
输入:text1 = "abc", text2 = "def"
输出:0
dp数组及下标含义
dp[i][j]表示text1下标i - 1之前部分与text2下标j - 1前的最长公共子序列长度
递推公式
如果text1[i - 1] == text2[j - 1],dp[i][j] = dp[i - 1][j - 1] + 1
如果不相等,dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
初始化
全都初始化为0
遍历顺序
从小到大
int longestCommonSubsequence(string text1, string text2){
vector<vector<int>> dp(text1.length() + 1, vector<int>(text2.length() + 1, 0));
for(int i = 1; i <= text1.length(); i++){
for(int j = 1; j <= text2.length(); j++){
if(text1[i] == text2[j])
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.length()][text2.length()];
}
不同的子序列
dp数组及下标含义
dp[i][j]表示字符串s下标为i - 1的部分含t下标为j - 1部分的个数
疑问:为什么dp数组的i表示字符串的下标为i - 1,而不是i?
答:为了初始化简单,如果表示为下标i,以dp[0][j]为例,如果s[0] == t[0],dp[0][0]初始化为1,反之,初始化为0,dp[0][j] = dp[0][j - 1],如果s[0] == t[j],dp[0][j] += 1
递推公式
如果s[i - 1] == t[j - 1],dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]
疑问:为什么要加dp[i - 1][j]?
答:因为s[i - 1]与t[j - 1]匹配,还可能与t[j]匹配
如果s[i - 1] != t[j - 1],dp[i][j] = dp[i - 1][j]
初始化
dp[0][j]都初始化为0,因为s为空字符串,不可能包含t
dp[i][0]都初始化为1,因为t为空字符串,s删除全部元素,就是t
遍历顺序
从小到大
int numDistinct(string s, string t) {
vector<vector<int>> dp(s.length() + 1, vector<int>(t.length() + 1, 0));
for(int i = 0; i <= s.length(); i++)
dp[i][0] = 1;
for(int i = 1; i <= s.length(); i++){
for(int j = 1; j <= t.length(); j++){
if(s[i - 1] == t[i - 1])
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
else
dp[i][j] = dp[i - 1][j];
}
}
return dp[s.length()][t.length()];
}
子序列连续问题
最长连续递增序列
题目:674. 最长连续递增序列 - 力扣(LeetCode)
dp数组及下标含义
dp[i]表示到第i个元素时,最长连续递增子序列的长度
递推公式
如果(dp[i] > dp[i - 1]),dp[i] = dp[i - 1] + 1
初始化
dp数组全部初始化为1
遍历顺序
从小到大
int findLengthOfLCIS(vector<int> nums){
int result = 1;
vector<int> dp(nums.size(), 1);
for(int i = 1; i < nums.size(); i++){
if(dp[i] > dp[i - 1])
dp[i] = dp[i - 1] + 1;
if(result < dp[i])
result = dp[i];
}
return result;
}
最长重复子数组
题目:718. 最长重复子数组 - 力扣(LeetCode)
dp数组及下标含义
dp[i][j]表示数组A的下标i前部分和数组B的下标j前部分重复子数组的长度
递推公式
如果A[i] == B[j],dp[i][j] = dp[i - 1][j - 1] + 1
初始化
如果A[i] == B[j],dp[i][j] = 1,否则dp[i][j] = 0
遍历顺序
从小到大
int findLength(vector<int> A, vector<int> B){
int result = 0;
vector<vector<int>> dp(A.size(), vector<int>(B.size(), 0));
for(int i = 0; i < A.size(); i++){
for(int j = 0; j < B.size(); j++){
if(A[i] == B[j])
dp[i][j] = 1;
}
}
for(int i = 1; i < A.size(); i++){
for(int j = 1; j < B.size(); j++){
if(A[i] == B[j])
dp[i][j] = dp[i - 1][j - 1] + 1;
if(result < dp[i][j])
result = dp[i][j];
}
}
return result;
}
最大子序和
dp数组及下标含义
dp[i]表示[0, i]之间的连续子数组的最大和
递推公式
dp[i] = max(dp[i - 1] + nums[i], nums[i])
初始化
dp[0]初始化为nums[i]
遍历顺序
从小到大
int maxSubArray(vector<int>& nums) {
int result = 0;
vector<int> dp(nums.size(), 0);
dp[0] = nums[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;
}
编辑距离
编辑距离
题目:给定两个单词word1和word2,计算出将word1转换成word2所使用的最少操作数。可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例1:
输入:word1 = "horse", word2 = "ros"
输出:3
示例2:
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention
inention -> enention
enention -> exention
exention -> exection
exection -> execution
dp数组及下标含义
dp[i][j]表示将以下标i - 1结尾的word1转换为以下标j - 1结尾的word2最少需要的操作数
递推公式
如果word1[i - 1] == word2[j - 1],不需要编辑,dp[i][j] = dp[i - 1][j - 1]
反之,需要编辑
增删操作(因为word1增加一个元素,相当于把word2对应元素删除,所以放一起)
dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + 1
替换操作
dp[i][j] = dp[i - 1][j - 1] + 1
综上,word1[i - 1] != word2[j - 1]时,dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1]))
初始化
dp[0][j]和dp[i][0]都是要把其中一个字符串转换为空串的最少操作数,即字符串的长度
遍历顺序
从小到大
int minDistance(string word1, string word2){
vector<vector<int>> dp(word1.length() + 1, vector<int>(word2.length() + 1));
for(int i = 0; i <= word1.length(); i++)
dp[i][0] = i;
for(int j = 0; j <= word2.length(); j++)
dp[0][j] = j;
for(int i = 1; i <= word1.length(); i++){
for(int j = 1; j <= word2.length(); 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 - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
return dp[word1.length()][word2.length()];
}
回文子串
回文子串
dp数组及下标含义
dp[i][j]表示下标i到下标j的部分是否为回文子串
递推公式
如果s[i] != s[j],dp[i][j] = false
如果s[i] == s[j]
j >= i + 2时,dp[i + 1][j - 1] == true,dp[i][j] = true
j < i + 2时,dp[i][j] = true
初始化
全部初始化为false
遍历顺序
i从大到小,j从小到大,j大于等于i
int countSubstrings(string s) {
int result = 0;
vector<vector<bool>> dp(s.length(), vector<bool>(s.length(), false);
for(int i = s.length() - 1; i >= 0; i--){
for(int j = i; j < s.length(); j++){
if(s[i] == s[j]){
if(j < i + 2){
dp[i][j] = true;
result++;
}
else if(dp[i + 1][j - 1] == true){
dp[i][j] = true;
result++;
}
}
}
}
return result;
}
最长回文子序列
题目:516. 最长回文子序列 - 力扣(LeetCode)
dp数组及下标含义
dp[i][j]表示下标i - j部分含有的回文子序列的最大长度
递推公式
如果s[i] == s[j],
如果j < i + 2,dp[i][j] = j - i + 1
反之,dp[i][j] = dp[i + 1][j - 1] + 2
反之,看左右两边哪个的最长回文子序列更长,dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
初始化
无需初始化
遍历顺序
i从大到小,j从小到大,j大于等于i
int longestPalindromeSubseq(string s) {
vector<vector<int>> dp(s.length(), vector<int>(s.length()));
for(int i = s.length(); i >= 0; i--){
for(int j = i; j < s.length(); j++){
if(s[i] == s[j]){
if(j < i + 2)
dp[i][j] = j - i + 1;
else
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.length() - 1];
}