LeetCode题解之动态规划(三)

230 阅读3分钟

新碰到的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;
    }
};