codetop标签动态规划大全C++讲解(四)!!动态规划刷穿地心!!学吐了家人们o(╥﹏╥)o

1.最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。

注意初始化,res需要初始为dp[0]

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int> dp(nums.size(), 0);
        dp[0] = nums[0];
        int res = dp[0];
        if(nums.size() <= 1) return dp[0];
        for(int i = 1; i < nums.size(); i ++){
            dp[i] = max(nums[i], dp[i - 1] + nums[i]);
            if(dp[i] > res) res = dp[i];
        }
        return res;
    }
};

2.最长的斐波那契子序列的长度

如果序列 X_1, X_2, …, X_n 满足下列条件,就说它是 斐波那契式 的:

  • n >= 3
  • 对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}

给定一个严格递增的正整数数组形成序列 arr ,找到 arr 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。
eg:
输入: arr = [1,2,3,4,5,6,7,8]
输出: 5
解释: 最长的斐波那契式子序列为 [1,2,3,5,8] 。
dp[i][j] 表示以 arr[i] 和 arr[j] 作为最后两个数字的最长斐波那契式子序列的长度。

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int n = arr.size();
        unordered_map<int, int> indexMap;
        for (int i = 0; i < n; i++) {
            indexMap[arr[i]] = i;
        }

        vector<vector<int>> dp(n, vector<int>(n, 2));
        int maxLen = 0;

        for (int j = 1; j < n; j++) {
            for (int i = 0; i < j; i++) {
                int k = indexMap.find(arr[j] - arr[i]) != indexMap.end() ? indexMap[arr[j] - arr[i]] : -1;
                if (k >= 0 && k < i) {
                    dp[i][j] = dp[k][i] + 1;
                    maxLen = max(maxLen, dp[i][j]);
                }
            }
        }

        return maxLen > 2 ? maxLen : 0;
    }
};

3.最大正方形

在一个由0和1组成的二维矩阵内,找到只包含1的最大正方形,并返回其面积

前缀和做法:
和美团2024春招第一题平衡矩阵很像,给一个和平衡矩阵一样的做法
需要注意的是,在小美的平衡矩阵中,输入数组都是下标1开始,这里的matrix是从0开始的,而前缀和又必须从1开始,所以前缀和s要加的matrix[i - 1][j - 1] - ‘0’
左上角i,j的起始,已经是在前缀和s里面看了,所以从1开始而不是0

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if (matrix.empty()) return 0;
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int>> s(m+1, vector<int>(n+1, 0));
        int res = 0;
        
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                s[i][j] = (matrix[i-1][j-1] - '0') + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
            }
        }
        
        // i 和 j 是左上角, l 和 r 是右下角
        // len 是正方形边长
        for (int len = 1; len <= min(m, n); len++) {
            for (int i = 1; i <= m - len + 1; i++) {
                for (int j = 1; j <= n - len + 1; j++) {
                    int l = i + len - 1;
                    int r = j + len - 1;
                    int total = s[l][r] - s[l][j - 1] - s[i - 1][r] + s[i - 1][j - 1];
                    if (total == len * len) res = max(res, len * len);
                }
            }
        }
        return res;
    }
};

动态规划做法:

  1. dp[i][j] 代表(i,j)为右下角,且只包含1的正方形的边长最大值。
  2. 递推公式:
  • 如果该位置是0,则dp[i][j] = 0,因为当前位置不可能由1组成的正方形中
  • 如果该位置是1,则dp[i][j]的值由其上方、左方和左上方的三个相邻位置的dp值决定。具体而言,当前位置的元素值等于三个相邻位置的元素中的最小值加 1,状态转移方程如下:dp(i, j)=min(dp(i−1, j), dp(i−1, j−1), dp(i, j−1))+1
  1. 如果 i 和 j 中至少有一个为0,则位置(i,j)为右下角的最大正方形的边长只能是1,dp[i][j] = 1
class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if(matrix.empty()) return 0;
        int m = matrix.size();
        int n = matrix[0].size();
        vector<vector<int>> dp(m, vector<int>(n, 0));
        int len = 0;
        for(int i = 0; i < m; i ++){
            for(int j = 0; j < n; j ++){
                if(matrix[i][j] == '1'){
                    if(i == 0 || j == 0) dp[i][j] = 1;
                    else dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
                    if(dp[i][j] > len) len = dp[i][j];
                }
            }
        }
        return len * len;
    }
};

4.最长有效括号

给一个字符串只包含 ‘(’ 和 ‘)’ ,找出最长有效括号子串。
在这里插入图片描述
dp[i]的含义是以下标1结尾的字符串最长有效括号子串的长度
所以上图dp[1] = 2, dp[3] = 4

  1. 如果当前遍历到了‘(’,那么一定是非法序列,dp[i] = 0
  2. 如果当前遍历到了‘)’,那么分2种情况
  • )的前面是(,那么dp[i] = dp[i - 2] + 2
  • )的前面是),那么需要检查i - dp[i - 1] - 1,即前一个合法序列的前一个位置是不是左括号,类似于图中的dp[7],index = 7 的时候,此时 index - 1 也是右括号,我们需要知道 i - dp[i - 1] - 1 = 7 - dp [ 6 ] - 1 = 4 位置的括号的情况。而刚好 index = 4 的位置是左括号,此时 dp [ i ] = dp [ i - 1 ] + dp [ i - dp [ i - 1] - 2 ] + 2 (当前位置的前一个合法序列的长度,加上匹配的左括号前边的合法序列的长度,加上新增的长度 2)
class Solution {
public:
    int longestValidParentheses(string s) {
        vector<int> dp(s.size(), 0);
        int res = 0;
        for(int i = 1; i < s.size(); i ++){
            if(s[i] == ')'){
                if(s[i - 1] == '('){
                    dp[i] = (i - 2 >= 0 ? dp[i - 2] : 0) + 2;
                }else if(i > 0 && i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] == '('){
                    dp[i] = dp[i - 1] + (i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
            }
            res = max(res, dp[i]);
        }
        return res;
    }
};

5.乘积最大子数组

给你一个整数数组nums,请你找出数组中乘积最大的非空连续子数组,并将乘积返回
dpMax[i] 表示以第 i 个元素的结尾的子数组,乘积最大的值

  1. 当nums[i] >= 0 并且dpMax[i - 1] > 0,dpMax[i] = dpMax[i - 1] * nums[i]
  2. 当nums[i] >= 0 并且dpMax[i - 1] < 0,dpMax[i] = nums[i]
  3. 当nums[i] < 0时,dpMax需要分情况讨论
  • 当dpMin[i - 1] < 0,dpMax[i] = dpMin[i - 1] * nums[i]
  • 当dpMin[i - 1] >= 0,dpMax[i] = nums[i]

上面引入了dpMin数组,怎么求dpMin和dpMax其实是一样的。
首先
dpMax[i] = max(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i], nums[i]);
求 dpMin[i] 同理
dpMin[i] = min(dpMax[i-1] * nums[i], dpMin[i-1] * nums[i], nums[i]);
由于都是上一个dpMax或者上一个dpMin推导的,所以可以不设数组
dpMax = max(dpMin * nums[i], max(dpMax * nums[i], (double)nums[i]));
dpMin = min(dpMin * nums[i], min(preMax * nums[i], (double)nums[i]));
如果是int的话会有一个测试用例过不了

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) {
            return 0;
        }
        double dpMax = nums[0];
        double dpMin = nums[0];
        double maxProd = nums[0];
        for (int i = 1; i < n; i++) {
        	// 更新dpMin的时候需要dpMax之前的信息,所以先保存起来
            double preMax = dpMax;
            dpMax = max(dpMin * nums[i], max(dpMax * nums[i], (double)nums[i]));
            dpMin = min(dpMin * nums[i], min(preMax * nums[i], (double)nums[i]));
            maxProd = max(maxProd, dpMax);
        }
        return (int)maxProd; // 将结果转换回int,假设最终结果适合int类型。
    }
};

6.可被三整除的最大和

给你一个整数数组 nums,请你找出并返回能被三整除的元素 最大和。

dp[i][j]:处理前i个元素,余数为j的最大和。j有三种可能0,1,2按照定义,最终答案就是dp[n][0]
对于nums[i - 1],可选可不选

  1. 如果nums[i - 1]%3 = 0,那么说明加入当前nums[i - 1]不会改变其余数,所以:
  • dp[i][0] = max{dp[i - 1][0],dp[i - 1][0] + nums[i - 1]}
  • dp[i][1] = max{dp[i - 1][1],dp[i - 1][1] + nums[i - 1]}
  • dp[i][2] = max{dp[i - 1][2],dp[i - 1][2] + nums[i - 1]}
  1. 如果nums[i - 1]%3 = 1,那么说明加入当前nums[i - 1]之后,原来余数0会变成1,1变成2,2变成0
  • dp[i][0] = max{dp[i - 1][0],dp[i - 1][2] + nums[i - 1]}
  • dp[i][1] = max{dp[i - 1][1],dp[i - 1][0] + nums[i - 1]}
  • dp[i][2] = max{dp[i - 1][2],dp[i - 1][1] + nums[i - 1]}
  1. 如果nums[i − 1]%3 = 2,那么说明加入当前nums[ i − 1]之后,原来的余数0会变成2,1变成 0,2变成1:
  • dp[i][0] = max{dp[i - 1][0],dp[i - 1][1] + nums[i - 1]}
  • dp[i][1] = max{dp[i - 1][1],dp[i - 1][2] + nums[i - 1]}
  • dp[i][2] = max{dp[i - 1][2],dp[i - 1][0] + nums[i - 1]}
class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        int n = nums.size();
        int dp[n + 1][3];// dp[i][j]代表下标0~i-1的数中,÷3余数为j的最大和

        memset(dp,0,sizeof dp);
        dp[0][1] = -1e9 , dp[0][2] = -1e9;// dp[0][0]是前0个数。÷3余数为0的最大和,是0

        for(int i = 1;i <= n;i++){
            int r = nums[i - 1] % 3;
            if(r == 0){
                dp[i][0] = max(dp[i - 1][0] , dp[i - 1][0] + nums[i - 1]);
                dp[i][1] = max(dp[i - 1][1] , dp[i - 1][1] + nums[i - 1]);
                dp[i][2] = max(dp[i - 1][2] , dp[i - 1][2] + nums[i - 1]);
            }
            else if(r == 1){
                dp[i][0] = max(dp[i - 1][0] , dp[i - 1][2] + nums[i - 1]);
                dp[i][1] = max(dp[i - 1][1] , dp[i - 1][0] + nums[i - 1]);
                dp[i][2] = max(dp[i - 1][2] , dp[i - 1][1] + nums[i - 1]);
            }
            else{
                dp[i][0] = max(dp[i - 1][0] , dp[i - 1][1] + nums[i - 1]);
                dp[i][1] = max(dp[i - 1][1] , dp[i - 1][2] + nums[i - 1]);
                dp[i][2] = max(dp[i - 1][2] , dp[i - 1][0] + nums[i - 1]);
            }
        }
        return dp[n][0];
    }
};

7.回文子串数目

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

如果设置 dp[i] 的意义是指以下标 i 结尾的字符串有 dp[i] 个回文串,那就会很难想,因为dp[i] 和 dp[i-1] ,dp[i + 1] 看上去都没啥关系
所以应该这样设置:bool类型dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
假设s[i] != s[j],不是回文子串
s[i] == s[j],j - i <= 1,是回文串(a或aa的情况),如果 i 和 j 差距大于1,就撤回一步看dp[i + 1][j - 1]是不是回文子串

注意初始化,第二个for的j要从i开始不是i+1,差一步都不行

class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int res = 0;
        for(int i = s.size() - 1; i >= 0; i --){
            for(int j = i; j < s.size(); j ++){
                if(s[i] == s[j]){
                    if(j - i <= 1){
                        res++;
                        dp[i][j] = true;
                    }else{
                        if(dp[i + 1][j - 1]){
                            res++;
                            dp[i][j] = true;
                        }
                    }
                }
            }
        }
        return res;
    }
};

8.最长回文子序列

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。
子序列,所以可以不连续

大致逻辑和3一样
在s[i] == s[j]时,dp[i][j] = dp[i + 1][j - 1] + 2
在s[i] != s[j]时,加入s[j]的回文子序列长度为dp[i + 1][j]。加入s[i]的回文子序列长度为dp[i][j - 1]。

从递推式可以看出来,i == j的情况没有覆盖,所以需要初始化为1

另外加入s长度只有一个的话,无法进入for循环,这个时候应该输出1,所以res也需要初始化为1

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        int res = 1;
        for(int i = 0; i < s.size(); i ++) dp[i][i] = 1;
        for(int i = s.size() - 1; i >= 0; i --){
            for(int j = i + 1; j < s.size(); j ++){
                if(s[i] == s[j]) dp[i][j] = dp[i + 1][j - 1] + 2;
                else dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);

                if(dp[i][j] > res) res = dp[i][j];
            }
        }
        return res;
    }
};

9.最长回文子串

给你一个字符串 s,找到 s 中最长的 回文子串。
和3差不多

class Solution {
public:
    string longestPalindrome(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int l = 0, maxLen = -1;
        for(int i = s.size() - 1; i >= 0; i --){
            for(int j = i; j < s.size(); j ++){
                if(s[i] == s[j]){
                    if(j - i <= 1) dp[i][j] = true;
                    else if(dp[i + 1][j - 1]) dp[i][j] = true;
                }
                if(dp[i][j] && j - i + 1 > maxLen){
                    maxLen = j - i + 1;
                    l = i;
                }
            }
        }        
        return s.substr(l, maxLen);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值