之前系统地写了动态规划的常见题目,已经能够解决大部分动态规划相关问题。但是,还是存在一些题目,它的递推公式很难想到。所以,新开这一篇文章来记录一下,一方面,提高从题干提取递推公式的能力;另一方面,经常回顾,避免遇到原题还不会做。
把数字翻译成字符串
题目:把数字翻译成字符串_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
dp[i]表示长度为i的字符串可能的译码种数
递推关系
如果结尾的字符不为0,dp[i] += dp[i - 1]
如果最后两个字符组成的数字在10 - 26之间,dp[i] += dp[i - 2]
初始化
dp[0]初始化为1(i等于2时,数字在10 - 26之间,dp[2] += dp[0],所以dp[0] = 1)、
如果nums[0]不为‘0’,dp[1] = 1
其余初始化为0
遍历顺序
从2开始,从小到大
int solve(string nums) {
vector<int> dp(nums.size() + 1, 0);
dp[0] = 1;
if(nums[0] != '0')
dp[1] = 1;
for(int i = 2; i <= nums.size(); i++){
if(nums[i - 1] != '0')
dp[i] += dp[i - 1];
string t = nums.substr(i - 2, 2);
int n = atoi(t.c_str());
if(n >= 10 && n <= 26)
dp[i] += dp[i - 2];
}
return dp[nums.size()];
}
正则表达式匹配
题目:正则表达式匹配_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
dp[i][j]表示str下标i - 1前部分与pattern下标j - 1部分是否匹配
递推关系
如果str[i - 1] == pattern[j - 1]或者pattern[j - 1] == '.',dp[i][j] = dp[i - 1][j - 1]
如果pattern[j - 1] == '*',两种情况:
'*'表示前面的字符出现0次,j往后移两位,dp[i][j] = dp[i][j - 2]
'*'表示前面的字符出现多次,i往后移一位判断与'*'前一位是否匹配,即下标i与'*'后一位配对,dp[i][j] = dp[i - 1][j]
初始化
dp[0][0] = true
dp[i][0] = false(i大于0)
当pattern为"a*a*"这类,dp[0][j] = true
其余为false
遍历顺序
从小到大
bool match(string str, string pattern) {
vector<vector<bool>> dp(str.length() + 1, vector<bool>(pattern.length() + 1, false));
dp[0][0] = true;
for(int j = 2; j <= pattern.length(); j++)
dp[0][j] = dp[0][j - 2] && pattern[j - 1] == '*';
for(int i = 1; i <= str.length(); i++){
for(int j = 1; j <= pattern.length(); j++){
if(str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.')
dp[i][j] = dp[i - 1][j - 1];
else if(pattern[j - 1] == '*'){
if(j > 1)
dp[i][j] = dp[i][j - 2];
if(!dp[i][j] && (str[i - 1] == pattern[j - 2] || pattern[j - 2] == '.'))
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[str.length()][pattern.length()];
}
通配符匹配
题目:通配符匹配_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
dp[i][j]表示s下标i- 1前部分与p下标j - 1前部分是否匹配
递推公式
如果s[i - 1] == p[j - 1]或者p[j - 1] == '?',dp[i][j] = dp[i - 1][j - 1]
如果p[j - 1] == '*',两种情况:
忽略'*',即j前移一位,dp[i][j] = dp[i][j - 1]
'*'匹配任意长度的字符串,即i前移一位,dp[i][j] = dp[i - 1][j]
初始化
dp[0][0] = true
dp[i][0] = false(i大于0)
如果p全为'*',dp[0][j] = true
遍历顺序
从小到大
bool isMatch(string s, string p) {
vector<vector<bool>> dp(s.length() + 1, vector<bool>(p.length() + 1, false));
dp[0][0] = true;
for(int j = 1; j <= p.length(); j++){
if(p[j - 1] == '*')
dp[0][j] = true;
else
break;
}
for(int i = 1; i <= s.length(); i++){
for(int j = 1; j <= p.length(); j++){
if(s[i - 1] == p[j - 1] || p[j - 1] == '?')
dp[i][j] = dp[i - 1][j - 1];
if(p[j - 1] == '*')
dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
}
}
return dp[s.length()][p.length()];
}
最长的括号子串
题目:最长的括号子串_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
dp[i]表示以下标i结尾的字符串含义的括号子串的最大长度
递推公式
遇到')'才有可能是合法括号,所以我们只考虑"()"和"))"两种情况
"()":dp[i] = dp[i - 2] + 2
"))":需要先判断右边的')'是否有对应的'(',其下标为i - dp[i - 1] - 1。若匹配,则dp[i]为'('前的最长括号子串,加上')'前的最长括号子串,加上括号的长度2,dp[i] = dp[i - dp[i - 1] - 2] + dp[i - 2] + 2
初始化
dp[0] = 0
如果"()",dp[1] = 2,否则dp[1] = 0
遍历顺序
从小到大
int longestValidParentheses(string s) {
if(s.length() < 2)
return 0;
vector<int> dp(s.length(), 0);
int res = 0;
if(s[0] == '(' && s[1] == ')'){
dp[1] = 2;
res = 2;
}
for(int i = 2; i < s.length(); i++){
if(s[i] == ')' && s[i - 1] == '(')
dp[i] = dp[i - 2] + 2;
if(s[i] == ')' && s[i - 1] == ')' && i - dp[i - 1] >= 1 && s[i - dp[i - 1] - 1] == '('){
if(i - dp[i - 1] == 1)
dp[i] = dp[i - 1] + 2;
else
dp[i] = dp[i - dp[i - 1] - 2] + dp[i - 1] + 2;
res = max(res, dp[i]);
}
return res;
}
最大正方形
题目:最大正方形_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
dp[i][j]表示以下标[i - 1][j - 1]为右下角的最大正方形的边长
递推公式
向左上角找正方形,如果matrix[i][j] == '1',则可以找到正方形,dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1
初始化
全部初始化为0,matrix[i][j] == '0'时,边长为0
遍历顺序
从小到大
int solve(vector<vector<char> >& matrix) {
if(matrix.size() == 0)
return 0;
vector<vector<int>> dp(matrix.size() + 1, vector<int>(matrix[0].size() + 1, 0));
int edge = 0;
for(int i = 1; i <= matrix.size(); i++){
for(int j = 1; j <= matrix[0].size(); j++){
if(matrix[i - 1][j - 1] == '1')
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
edge = max(edge, dp[i][j]);
}
}
return edge * edge;
}
最大矩形
题目:最大矩形_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
dp[i][j]表示以下标[i, j]为右下角的矩形的pair,记录长和宽
递推公式
如果matrix[i][j] == '1',从左方和上方找最大矩形
初始化
dp[0][j]长为连续'1'的长度,宽为1
dp[i][0]长为1,宽为连续'1'的长度
遍历顺序
从小到大
int maximalRectangle(vector<vector<int> >& matrix) {
int res = 0, count = 0;
vector<vector<pair<int, int>>> dp(matrix.size(),vector<pair<int, int>>(matrix[0].size()));
for (int i = 0; i < matrix.size(); i++) {
if (matrix[i][0] == 1)
count++;
else
count = 0;
if (res < count)
res = count;
dp[i][0] = pair<int, int>(1, count);
}
count = 0;
for (int j = 0; j < matrix[0].size(); j++) {
if (matrix[0][j] == 1)
count++;
else
count = 0;
if (res < count)
res = count;
dp[0][j] = pair<int, int>(count, 1);
}
for (int i = 1; i < matrix.size(); i++) {
for (int j = 1; j < matrix[0].size(); j++) {
if (matrix[i][j] == 0)
dp[i][j] = pair<int, int>(0, 0);
else {
dp[i][j] = pair<int, int>(1, 1);
if (dp[i - 1][j].first != 0 && dp[i - 1][j].second != 0) {
int width = 0, height = 0;
while (width < dp[i - 1][j].first && j - width >= 0) {
if (matrix[i][j - width] == 1)
width++;
else
break;
}
while (height <= dp[i - 1][j].second && i - height >= 0) {
if (matrix[i - height][j] == 1)
height++;
else
break;
}
if(dp[i][j].first * dp[i][j].second < width * height)
dp[i][j] = pair<int, int>(width, height);
}
if(dp[i][j -1].first != 0 && dp[i][j - 1].second != 0){
int width = 0, height = 0;
while (width < dp[i - 1][j].first && j - width >= 0) {
if (matrix[i][j - width] == 1)
width++;
else
break;
}
while (height <= dp[i - 1][j].second && i - height >= 0) {
if (matrix[i - height][j] == 1)
height++;
else
break;
}
if(dp[i][j].first * dp[i][j].second < width * height)
dp[i][j] = pair<int, int>(width, height);
}
}
if (res < dp[i][j].first * dp[i][j].second)
res = dp[i][j].first * dp[i][j].second;
}
}
return res;
}
连续子数组的最大乘积
题目:连续子数组的最大乘积_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
pos[i]表示到下标i时的最大乘积,neg[i]表示到下标i时的最小乘积
递推公式
如果nums[i]为非负数,则pos[i] = max(pos[i - 1] * nums[i], nums[i]),neg[i] = min(neg[i - 1] * nums[i], nums[i])
如果nums[i]为负数,则pos[i] = max(neg[i - 1] * nums[i], nums[i]),neg[i] = min(pos[i] * nums[i], nums[i])
初始化
pos[0]和neg[0]初始化为nums[0]
遍历顺序
从小到大
int maxProduct(vector<int>& nums) {
int res = nums[0];
vector<int> pos(nums.size(), nums[0]), neg(nums.size(), nums[0]);
for(int i = 1; i < nums.size(); i++){
if(nums[i] >= 0){
pos[i] = max(pos[i - 1] * nums[i], nums[i]);
neg[i] = min(neg[i - 1] * nums[i], nums[i]);
}
else{
pos[i] = max(neg[i - 1] * nums[i], nums[i]);
neg[i] = min(pos[i - 1] * nums[i], nums[i]);
}
res = max(res, nums[i]);
}
return res;
}
填充数组
题目:填充数组_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
dp[i][j]表示有i个0需要填充,可以从j个数取值的方案数
递推公式
dp[i][j] = = dp[i - 1][j] + = dp[i - 1][j] + dp[i][j - 1]
初始化
dp[1][j] = j(为什么不是初始化dp[0][j]?因为dp[0][j] = 0,i= 1时不满足递推公式), dp[i][0] = 0
遍历顺序
从小到大
int FillArray(vector<int>& a, int k) {
int mod = 1000000007, i = 0, start, minv, end, maxv, res = 1;
vector<vector<int>> dp(a.size() + 1, vector<int>(k + 1, 0));
for(int j = 1; j <= k; j++)
dp[1][j] = j;
for(int i = 2; i <= a.size(); i++){
for(int j = 1; j <= k; j++){
dp[i][j] = (dp[i][j - 1] + dp[i - 1][j]) % mod;
}
}
while(i < a.size()){
while(i < a.size() && a[i] != 0)
i++;
if(i == a.size())
break;
start = i;
minv = (i == 0? 1:a[i - 1]);
while(i < a.size() && a[i] == 0)
i++;
end = i;
maxv = (i == a.size()? k:a[i]);
res = ((long long)res * dp[end - start][maxv - minv + 1]) % mod;
}
return res;
}
压缩字符串2
题目:压缩字符串(二)_牛客题霸_牛客网 (nowcoder.com)
dp数组及下标含义
dp[i][j]表示长度为i的字符串,删除j个字符,压缩后的字符串长度
递推公式
如果删除当前字符,dp[i][j + 1] = dp[i - 1][j]
如果不删除当前字符,如果后面与当前字符不同的个数小于j,则全部删除
初始化
dp[0][j] = 0
其余初始化为INT_MAX >> 1
遍历顺序
从小到大
int compressString(string s, int k) {
vector<vector<int>> dp(s.length() + 1, vector<int>(k + 1, INT_MAX >> 1));
dp[0][0] = 0;
for(int i = 1; i <= s.length(); i++){
for(int j = 0; j <= k && j <= k; j++){
if(j < k)
dp[i][j + 1] = min(dp[i][j + 1], dp[i - 1][j]);
int same = 0, diff = 0;
for(int m = i; m <= s.length(); m ++){
if(s[m - 1] == s[i - 1])
same++;
else
diff++;
if(j + diff > k)
break;
dp[m][j + diff] = min(dp[m][j + diff], len(same) + dp[i - 1][j]);
}
}
}
return dp[s.length()][k];
}
int len(int same){
if(same == 1)
return 1;
if(same < 10)
return 2;
if(same < 100)
return 3;
return 4;
}
信封嵌套问题
题目:信封嵌套问题_牛客题霸_牛客网 (nowcoder.com)
信封A的长和宽都比B的大,就可以嵌套。所以按长从小到大进行排序,就可以转换为宽的最长连续子序列问题
int maxLetters(vector<vector<int> >& letters) {
sort(letters.begin(), letters.end(), Compare);
int res = 1;
vector<int> dp(letters.size(), 1);
for(int i = 1; i < letters.size(); i++){
for(int j = 0; j < i; j++){
if(letters[i][1] > letters[j][1])
dp[i] = max(dp[i], dp[j] + 1);
}
if(res < dp[i])
res = dp[i];
}
return res;
}
static bool Compare(vector<int> x, vector<int> y){
if(x[0] == y[0])
return x[1] > y[1];
return x[0] < y[0];
}