新碰到的dp问题~
1. 区间dp
312. 戳气球
class Solution {
public:
/*
1.状态表示:f[i][j]表示(i,j)间所有气球集合
2.状态属性:max
3.状态转移:j-i>=2时,f[i][j] = f[i][k]+f[k][j]+p[i]*p[k]*p[j]
*/
class Solution {
public:
/*
1.状态表示:f[i][j]表示(i,j)间所有气球集合,注意开区间
2.状态属性:max
3.状态转移:j-i>=2时,f[i][j] = f[i][k]+f[k][j]+p[i]*p[k]*p[j],k是最后一个被戳爆的气球
*/
int maxCoins(vector<int>& nums) {
int n = nums.size();
vector<int> p(n + 2, 1);
for(int i = 0; i < n; i ++) p[i + 1] = nums[i];
vector<vector<int>> f(n + 2, vector<int> (n + 2, 0));
for(int len = 1; len <= n + 2; len ++)
for(int i = 0; i + len - 1 <= n + 1; i ++)
{
int j = i + len - 1;
for(int k = i + 1; k < j; k ++)
f[i][j] = max(f[i][j] , f[i][k] + f[k][j] + p[i] * p[k] * p[j]);
}
return f[0][n + 1];
}
};
647. 回文子串
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
if(n == 0) return 0;
int ans = n;
vector<vector<bool>> f(n, vector<bool>(n, false));
for(int i = 0; i < n; i ++)
{
f[i][i] = true;//对角线单个的为true
}
for(int len = 2; len <= n; len ++)
for(int i = 0; i + len - 1 < n; i ++)
{
int j = i + len - 1;
if(len > 2 && f[i + 1][j - 1] && s[i] == s[j] || len == 2 && s[i] == s[j])
{
ans ++;
f[i][j] = true;
}
}
return ans;
}
};
5. 最长回文子串
class Solution {
public:
/*
1.状态表示:f[n][n],字符串输入[0,n-1]对应[1,n]
2.状态属性:bool
3.状态转移:i表示子串起点,j表示终点,f[i][j]就是[i,j]是否为回文串:f[i][j]=f[i+1][j-1]&&s[i-1]==s[j-1],但需要判断边界情况
*/
string longestPalindrome(string s) {
int n = s.size();
vector<vector<bool>> f(n, vector<bool>(n, false));
for(int i = 0; i < n; i ++) f[i][i] = true;//对角线单个的为true
int max_len = 1;
int start = 0;
for(int len = 2; len <= n; len ++)
for(int i = 0; i + len - 1 < n; i ++)
{
int j = i + len - 1;
if(len > 2 && f[i + 1][j - 1] && s[i] == s[j] || len == 2 && s[i] == s[j])
{
f[i][j] = true;
if(len > max_len) //遇到更长的长度
{
max_len = len;
start = i;
}
}
}
return s.substr(start,max_len);
}
};
664. 奇怪的打印机
class Solution {
public:
int strangePrinter(string s) {
if(s.empty()) return 0;
int n = s.size();
vector<vector<int>> f(n + 1, vector<int> (n+1));
for(int len = 1; len <= n; len ++)
for(int i = 0; i +len -1 < n; i ++)
{
int j = i + len - 1;
f[i][j] = f[i+1][j] + 1;//与区间内每一个字母都不同
for(int k = i + 1; k <= j; k ++)//与第k个相同,则第k个可以忽略
if(s[k] == s[i])
f[i][j] = min(f[i][j], f[i][k-1] + f[k+1][j]);
}
return f[0][n-1];
}
};
2.有点trick的dp
309. 最佳买卖股票时机含冷冻期
用到了状态机转移的理解方式
class Solution {
public:
/*
1.状态表示:f[n][2],每天结束时有2种状态:持有和不持有
2.状态属性:max
3.状态转移:
3.1 f[i][0]当天不持有:前一天就不持有f[i-1][0]或者今天卖出去了f[i-1][1]+p[i]
3.2 f[i][1]当天持有:前一天就持有f[i-1][1]或者今天才买的f[i-2][0]-p[i]
*/
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n <= 1) return 0;
vector<vector<int>> f(n + 1,vector<int> (2));
//初始化:第0天都初始化为0,第1天不持有的话肯定收益是0,持有的话收益肯定是负数
f[0][0] = f[0][1] = f[1][0] = 0;
f[1][1] = -prices[0];
//从第2天开始dp
for(int i = 2; i <= n; i ++)
{
f[i][0] = max(f[i - 1][0], f[i - 1][1] + prices[i - 1]);
f[i][1] = max(f[i - 1][1], f[i - 2][0] - prices[i - 1]);
}
return f[n][0];//返回的值肯定是当天不持有的
}
};
337. 打家劫舍 III
用到了两个dp数组
/*
1.f[o]表示选择o的情况下最大值,g[o]表示不选择o下的最大值
2.f(o) = o->val + g(l) + g(r);g(o)=max{f(l),g(l)}+max{f(r),g(r)}
*/
class Solution {
public:
unordered_map <TreeNode* ,int> f,g;
int rob(TreeNode* root) {
dfs(root);
return max(f[root], g[root]);
}
void dfs(TreeNode* root)
{
if(!root) return ;
dfs(root->left);
dfs(root->right);
f[root] = root->val + g[root->left] + g[root->right];
g[root] = max(f[root->left], g[root->left]) + max(f[root->right], g[root->right]);
}
};
338. 比特位计数
class Solution {
public:
/*
偶数时1010和101的其实一样,奇数时就再加1:res[i] = res[i >> 1] + (i & 1)
*/
vector<int> countBits(int num) {
vector<int> res(num + 1);
for(int i = 1; i <= num; i ++)
res[i] = res[i >> 1] + (i & 1);
return res;
}
};
221. 最大正方形
若形成正方形,以当前为右下角的视角看,则需要:当前格、上、左、左上都是1,也就是三个方向不被0限制的max值,所以是三个方向的dp[]中的min
class Solution {
public:
/*
状态表示:二维dp,为了方便补充一行一列的0
状态属性:max值
状态转移:格子为1时,dp(i, j) = min(dp(i - 1, j), dp(i, j - 1), dp(i - 1, j - 1)) + 1;
*/
int maximalSquare(vector<vector<char>>& matrix) {
if(matrix.size() == 0 || matrix[0].size() == 0)
return 0;
int maxside = 0;//记录全局max值
int rows = matrix.size(), cols = matrix[0].size();
vector<vector<int>> f(rows + 1, vector<int> (cols + 1,0));
//for(int i = 0; i<= rows; i ++) f[i][0] = 0;
//for(int i = 0; i<= cols; i ++) f[0][i] = 0;
for(int i = 1; i <= rows; i ++)
for(int j = 1; j <= cols; j ++)
{
if(matrix[i - 1][j - 1] == '1')
{
f[i][j] = min(min(f[i - 1][j], f[i][j - 1]), f[i - 1][j - 1]) + 1;
maxside = max(maxside, f[i][j]);
}
}
return maxside * maxside;
}
};
152. 乘积最大子数组
class Solution {
public:
int maxProduct(vector<int>& nums) {
//由于只和上一个状态有关,用滚动数组
//只用一个数组是不对的,因为这个数为负数的话会希望前面负得最多,所以存两个
int maxF = nums[0], minF = nums[0], ans = nums[0];
for (int i = 1; i < nums.size(); ++i) {
int mx = maxF, mn = minF;
maxF = max(mx * nums[i], max(nums[i], mn * nums[i]));//nums[i]表示单独开始
minF = min(mn * nums[i], min(nums[i], mx * nums[i]));
ans = max(maxF, ans);
}
return ans;
}
};