Leetcode区间DP
长区间可以划分成相同类型、只是长度不同的短区间,就可以用区间DP来做。
区间dp的模板:
memset(dp,0,sizeof(dp));
//初始dp数组
for(int len=2;len<=n;len++){
//枚举区间长度
for(int i=1;i<n;++i){//枚举区间的起点
int j=i+len-1;//根据起点和长度得出终点
if(j>n) break;//符合条件的终点
for(int k=i;k<=j;++k)//枚举最优分割点
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]);//状态转移方程
}
}
dp[i][j]的定义仍然是从i到j的最大值/最小值,但是不是枚举i和j,而是枚举长度len和起点i。
因为根据区间DP的思想,由小区间推出大区间,因此要按照区间的长度从小到大枚举。
Leetcode 面试题 08.14. 布尔运算
题解:看似是表达式计算,其实是个区间DP。
区间DP模板题,dp[i][j][k]表示i~j且结果为k的方案数,枚举分割点(以i j 中的运算符为分割点)
但是递推的时候不是枚举i和j,而是枚举len和i,也就是先算出所有长度为1的区间,在算长度为2的,在算3......
详情可参考https://leetcode-cn.com/problems/boolean-evaluation-lcci/solution/qu-jian-dpfen-zhi-suan-fa-by-whbsurpass-bsry/
class Solution {
public:
int countEval(string s, int result) {
int n = s.size();
int dp[n][n][2]; // dp[i][j][k] 表示从第i个开始长度为j,结果为k的方案数
memset(dp, 0, sizeof(dp));
for(int len = 0;len < n;len+=2) {
for(int i = 0;i < n;i+=2) {
int j = i+len;
if(j >= n) break;
if(len == 0) { dp[i][i][s[i]-'0'] = 1; continue; }
for(int k = i+1;k < j;k+=2) {
if(s[k] == '&') {
dp[i][j][0] += dp[i][k-1][0] * dp[k+1][j][0];
dp[i][j][0] += dp[i][k-1][0] * dp[k+1][j][1];
dp[i][j][0] += dp[i][k-1][1] * dp[k+1][j][0];
dp[i][j][1] += dp[i][k-1][1] * dp[k+1][j][1];
} else if(s[k] == '|') {
dp[i][j][0] += dp[i][k-1][0] * dp[k+1][j][0];
dp[i][j][1] += dp[i][k-1][0] * dp[k+1][j][1];
dp[i][j][1] += dp[i][k-1][1] * dp[k+1][j][0];
dp[i][j][1] += dp[i][k-1][1] * dp[k+1][j][1];
} else { // '^'
dp[i][j][0] += dp[i][k-1][0] * dp[k+1][j][0];
dp[i][j][1] += dp[i][k-1][0] * dp[k+1][j][1];
dp[i][j][1] += dp[i][k-1][1] * dp[k+1][j][0];
dp[i][j][0] += dp[i][k-1][1] * dp[k+1][j][1];
}
}
// cout << dp[i][j][result] << " ";
}
// cout << endl;
}
return dp[0][n-1][result];
}
};
Leetcode 1039. 多边形三角剖分的最低得分
题意:将多边形划分成多个三角形,求最小值
分析:dp[i][j]表示i~j的最小值,以ij为底,枚举k,ijk构成一个三角形,剩下的左右两边递归。
class Solution {
public:
int minScoreTriangulation(vector<int>& values) {
int n = values.size();
int dp[n][n]; // dp[i][j]表示从i到j的三角形剖分的最小值
memset(dp, 0, sizeof(dp));
for(int len = 2;len <= n;len++) { // 枚举长度
for(int i = 0;i < n;i++) { // 枚举起点
int j = i+len;
if(j >= n) break;
dp[i][j] = 0x3f3f3f3f;
for(int k = i+1;k < j;k++) { // 以ij边为底,枚举顶点k
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + values[i]*values[j]*values[k]);
}
}
}
return dp[0][n-1];
}
};
Leetcode 312. 戳气球
解法:这是一道hard,它的dp定义的有点特殊,dp[i][j] 表示不戳破ij、戳破中间的气球的最大值。
只要给初始数组前后各加1,大区间将能划分成同类型的小区间了。
逆着做,枚举区间最后一个戳破的
class Solution {
public:
int maxCoins(vector<int>& nums) {
nums.push_back(1);
nums.insert(nums.begin(), 1);
int n = nums.size();
int dp[n][n];
memset(dp, 0, sizeof(dp));
for(int len = 2;len < n;len++) {
for(int i = 0;i < n;i++) {
int j = i+len;
if(j >= n) break;
for(int k = i+1;k < j;k++) { // 枚举i~j中最后一个戳破的(先戳会影响两边)
dp[i][j] = max(dp[i][j], dp[i][k]+dp[k][j]+ nums[i]*nums[k]*nums[j]); // i j 不被戳破,因此左边剩下的就是i k ,右边就是 k j
}
}
}
return dp[0][n-1];
}
};
Leetcode 546. 移除盒子
题解:较难的dp,dp[i][j]没法形成子结构,像前面加padding逆着来也不行,说明要增加维度。
dp[i][j][k] 表示从 i 到 j 且后面有 k 个与相同的元素,枚举最后一个元素的合并情况
最后一个元素可以直接移除,也可以选择和(i, j-1)中的某一个一起移除。
详情可见:
https://leetcode-cn.com/problems/remove-boxes/solution/yuan-chuang-jie-fa-by-inszva-2/
https://leetcode-cn.com/problems/remove-boxes/solution/yi-chu-he-zi-by-leetcode-solution/
class Solution {
public:
int removeBoxes(vector<int>& boxes) {
int n = boxes.size();
int dp[n][n][n];
memset(dp, 0, sizeof(dp));
for(int len = 0;len < n;len++) {
for(int i = 0;i < n-len;i++) {
int j = i+len;
for(int k = 0;k < n-j;k++) {
dp[i][j][k] = max(dp[i][j][k], (j==0?0:dp[i][j-1][0]) + (k+1)*(k+1));
for(int t = i;t< j;t++) { //枚举分割点
if(boxes[t] == boxes[j]) {
dp[i][j][k] = max(dp[i][j][k], dp[i][t][k+1] + dp[t+1][j-1][0]);
}
}
}
}
}
return dp[0][n-1][0];
}
};
Leetcode 664. 奇怪的打印机
题解:区间dp,多层一个条件,枚举k的时候,只将nums[k] == nums[i] 的k作为分隔点
其实也是考虑第一个元素是单独打印,还是与区间内某个元素一起打印
class Solution {
public:
int strangePrinter(string s) {
int n = s.size();
int dp[n+1][n];
memset(dp, 0, sizeof(dp));
for(int len = 0;len < n;len++) {
for(int i = 0;i < n-len;i++) {
int j = i+len;
// cout << i << " " << j << endl;
dp[i][j] = 1 + dp[i+1][j]; // 将k==i的时候用于初始化
for(int k = i+1;k <= j;k++) {
if(s[k] == s[i]) { // 找和s[i]相同的字符作为分割点
dp[i][j] = min(dp[i][j], dp[i][k-1] + dp[k+1][j]);
}
}
}
}
return dp[0][n-1];
}
};
Leetcode 486. 预测赢家
题解:分别考虑左左,左右,右左,右右四种情况,进行区间DP
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
int n = nums.size();
if(n%2 == 0) return true; // 特判,偶数时先手必赢
int dp[n+5][n+5]; // dp[i][j]表示从i到j先手可以获得的最大值
memset(dp, 0, sizeof(dp));
for(int len = 0;len < n;len+=1) { // 枚举长度
for(int i = 1;i <= n-len;i++) { // 枚举起点
int j = i+len;
// cout << i << " " << j << " ";
dp[i][j] = max(dp[i][j], nums[i-1]+min(dp[i+2][j], dp[i+1][j-1])); // 取第一个
dp[i][j] = max(dp[i][j], nums[j-1]+min(dp[i+1][j-1], (j>=2? dp[i][j-2] : 0x3f3f3f3f))); // 取最后一个
// cout << dp[i][j] << endl;
}
}
int sum = 0;
for(int num : nums) sum += num;
if(dp[1][n]*2 >= sum) return true;
return false;
}
};
Leetcode 5. 最长回文串
题解:很简单的区间DP,dp[i][j] = (s[i]==s[j] && dp[i+1][j-1])
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
int dp[n+5][n+5];
memset(dp, 1, sizeof(dp));
int max_len = 1, max_i = 1, max_j = 1;
for(int len = 0;len < n;len++) { // 枚举长度
for(int i = 1;i <= n-len;i++) { // 枚举起点
int j = i+len;
dp[i][j] = (s[i-1] == s[j-1] && dp[i+1][j-1]);
if(dp[i][j] && len+1 > max_len) { // 记录最长回文串的位置
max_len = len+1;
max_i = i;
max_j = j;
}
}
}
return s.substr(max_i-1, max_j-max_i+1);
}
};
参考链接:【一文团灭区间DP】