动态规划专训4——子数组系列

本文详细探讨了动态规划在解决数组相关问题中的应用,包括最大子数组和、环形子数组和计算、乘积优化及字符串拆分,展示了如何通过状态转移方程求解这些问题。
摘要由CSDN通过智能技术生成

动态规划题目中,常出现子数组相关问题,这里单独挑出来训练

1.最大子树组和

53. 最大子数组和

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

子数组

是数组中的一个连续部分

1.状态表示:用dp[ i ]表示以第i个数组元素为结尾最大子数组的和

2.状态转移方程:dp[ i ] = max(dp[ i - 1 ] + nums[ i - 1 ] , nums[ i - 1 ] );

3.初始化:无需初始化

4.填表顺序:从左往右填

5.返回值:dp表中最大值

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

        return ret;
    }
};

这是ac代码

2.环形子数组的最大和

918. 环形子数组的最大和

给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 

环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。

子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n

1.状态表示:用 f [ i ]表示以i元素为子数组结尾,数组的最小和

                     用 g [ i ]表示以i元素为子数组结尾,数组的最大和

2.状态转移方程:

            f[ i ] = min(f [ i - 1 ] + nums[ i - 1 ], nums[ i - 1 ] );

            g[ i ] = max(g [ i - 1 ] + nums[ i - 1 ], nums[ i - 1] );

3.初始化:无需初始化

4.填表顺序:从左往右填

5.返回值:max(gmax, sum - fmin);

注意:当数组全是负数的时候,应该返回gmax,而不是sum - fmin(0)

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) 
    {
        int n = nums.size(), sum = 0;
        for(int e : nums) sum += e;

        vector<int> f(n + 1), g(n + 1);
        int fmin = INT_MAX, gmax = INT_MIN;
        for(int i = 1; i <= n; ++i)
        {
            f[i] = min(f[i - 1] + nums[i - 1], nums[i - 1]);
            g[i] = max(g[i - 1] + nums[i - 1], nums[i - 1]);

            fmin = min(fmin, f[i]);
            gmax = max(gmax, g[i]);
        }

        return fmin == sum ? gmax : max(gmax, sum - fmin);
    }
};

这是ac代码

3.乘积最大的子数组

152. 乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续

子数组

(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

测试用例的答案是一个 32-位 整数

1.状态表示:用 f [ i ]表示以i元素为子数组结尾,子数组的最大乘积

                     用 g [ i ]表示以i元素为子数组结尾,子数组的最小乘积

2.状态转移方程:

            f[ i ] = max(f[ i - 1 ] * nums [ i -  1], max(g[ i -  1] * nums[ i -  1], nums[ i - 1] ));

            g[ i ] = min(f[ i - 1 ] * nums [ i - 1 ], min(g [ i  - 1] * nums[ i - 1 ], nums[ i - 1 ]) );

3.初始化:f[0] = g[0] = 1;

4.填表顺序:从左往右两个表一起填

5.返回值:f表中的最大值

class Solution {
public:
    int maxProduct(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> f(n + 1), g(n + 1);
        f[0] = g[0] = 1;
        int ret = INT_MIN;
        for(int i = 1; i <= n; ++i)
        {
            f[i] = max(f[i - 1] * nums[i - 1], max(g[i - 1] * nums[i - 1], nums[i - 1]));
            g[i] = min(f[i - 1] * nums[i - 1], min(g[i - 1] * nums[i - 1], nums[i - 1]));

            ret = max(ret, f[i]);
        }

        return ret;
    }
};

这是ac代码

4.乘积为正数的最长数组长度

1567. 乘积为正数的最长子数组长度

给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。

一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。

请你返回乘积为正数的最长子数组长度

1.状态表示:用 f [ i ]表示以i元素为子数组结尾,乘积为正数的最大长度

                     用 g [ i ]表示以i元素为子数组结尾,乘积为负数的最大长度

2.状态转移方程:

nums[ i - 1 ] > 0

                f[ i ] = f[ i - 1] + 1;

                g[ i ] = g[ i - 1] == 0 ? 0 : g[ i - 1 ] + 1;

nums[i - 1] < 0

                f[ i ] = g[ i - 1] == 0 ? 0 : g[ i - 1 ] + 1;

                g[ i ] = f [ i - 1] + 1;

3.初始化:无需初始化

4.填表顺序:从左往右两个表一起填

5.返回值:f表中的最大值

class Solution {
public:
    int getMaxLen(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> f(n + 1), g(n + 1);
        int ret = 0;
        for(int i = 1; i <= n; ++i)
        {
            if(nums[i - 1] > 0)
            {
                f[i] = f[i - 1] + 1;
                g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;
            }
            else if(nums[i - 1] < 0)
            {
                f[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;
                g[i] = f[i - 1] + 1;
            }

            ret = max(ret, f[i]);
        }

        return ret;
    }
};

这是ac代码

5.等差数列划分

413. 等差数列划分

如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。

  • 例如,[1,3,5,7,9][7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。

给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组 个数。

子数组 是数组中的一个连续序列

1.状态表示:用dp[ i ]表示以第i个数组元素为结尾子数组的等差数列个数

2.状态转移方程:dp[ i ] = dp[ i - 1 ] + 1;

3.初始化:无需初始化

4.填表顺序:从左往右填

5.返回值:dp表的和

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) 
    {
        int n = nums.size();
        if(n < 3) return 0;
        vector<int> dp(n);
        int ret = 0;
        for(int i = 2; i < n; ++i)
        {
            if(nums[i] + nums[i - 2] == nums[i - 1] * 2)
                dp[i] = dp[i - 1] + 1;
            ret += dp[i];
        }   

        return ret;
    }
};

这是ac代码

6.最长湍流子数组

978. 最长湍流子数组

给定一个整数数组 arr ,返回 arr 的 最大湍流子数组的长度 

如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是 湍流子数组 。

更正式地来说,当 arr 的子数组 A[i], A[i+1], ..., A[j] 满足仅满足下列条件时,我们称其为湍流子数组

  • 若 i <= k < j :
    • 当 k 为奇数时, A[k] > A[k+1],且
    • 当 k 为偶数时,A[k] < A[k+1]
  • 或 若 i <= k < j :
    • 当 k 为偶数时,A[k] > A[k+1] ,且
    • 当 k 为奇数时, A[k] < A[k+1]

1.状态表示:用 f [ i ]表示以i元素为子数组结尾,湍流子数组以大结尾的最大长度

                     用 g [ i ]表示以i元素为子数组结尾,湍流子数组以小结尾的最大长度

2.状态转移方程:

                f[ i ] = g[ i - 1 ] + 1;  g[ i ]  = f[ i - 1 ] + 1;

3.初始化:全初始化为1

4.填表顺序:从左往右两个表一起填

5.返回值:两个表中的最大值

class Solution {
public:
    int maxTurbulenceSize(vector<int>& arr) 
    {
        int n = arr.size();
        vector<int> f(n, 1), g(n, 1);
        int ret = 1;

        for(int i = 1; i < n; ++i)
        {
            if(arr[i] > arr[i - 1])
                f[i] = g[i - 1] + 1;
            else if(arr[i] < arr[i - 1])
                g[i] = f[i - 1] + 1;
            
            ret = max(ret, max(f[i], g[i]));
        }

        return ret;
    }
};

这是ac代码

7.单词拆分

139. 单词拆分

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true

注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用

1.状态表示:用dp[ i ]表示以第i个数组元素为结尾单词拆分是否能成功

2.状态转移方程:if ( dp[ j - 1]  && hash.count(s.substr( j, i - j + 1 )) )

                                   dp[i] = true;

3.初始化:dp[0] = true

4.填表顺序:从左往右填

5.返回值:dp[n]

注意:为了对应下标,s需要特殊处理, s = ' ' + s;

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) 
    {
        int n = s.size();
        unordered_set<string> hash;
        for(auto& str : wordDict) hash.insert(str);

        vector<bool> dp(n + 1);
        dp[0] = true;
        s = ' ' + s;
        for(int i = 1; i <= n; ++i)
        {
            for(int j = i; j >= 1; --j)
                if(dp[j - 1] && hash.count(s.substr(j, i - j + 1)))
                {
                    dp[i] = true;
                    break;
                }
        }

        return dp[n];
    }
};

这是ac代码

8.环绕字符串中唯⼀的⼦字符串

467. 环绕字符串中唯一的子字符串

定义字符串 base 为一个 "abcdefghijklmnopqrstuvwxyz" 无限环绕的字符串,所以 base 看起来是这样的:

  • "...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....".

给你一个字符串 s ,请你统计并返回 s 中有多少 不同非空子串 也在 base 中出现

1.状态表示:用dp[ i ]表示以第i个数组元素为结尾所有子串在给定串中出现次数

2.状态转移方程:dp[ i ] = dp[ i - 1 ]  + 1;

3.初始化:全初始化为0

4.填表顺序:从左往右填

5.返回值:dp表中各个字母所对应的最大值

注意:由于要进行去重操作,以及题给特性,我们创建哈希表,记录各个字母对应最大数据即可

class Solution {
public:
    int findSubstringInWraproundString(string s) 
    {
        int n = s.size();
        vector<int> dp(n, 1);
        for(int i = 1; i < n; ++i)
            if(s[i] == s[i - 1] + 1 || s[i] == 'a' && s[i - 1] == 'z')
                dp[i] += dp[i - 1];
            
        int hash[26] = {0};
        for(int i = 0; i < n; ++i)
            hash[s[i] - 'a'] = max(dp[i], hash[s[i] - 'a']);

        int ret = 0;
        for(int e : hash) ret += e;

        return ret;
    }
};

这是ac代码

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值