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】

posted @ 2022-02-17 00:03  Rogn  阅读(160)  评论(0编辑  收藏  举报