动态规划题目中,常出现子数组相关问题,这里单独挑出来训练
1.最大子树组和
给你一个整数数组 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.环形子数组的最大和
给定一个长度为 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.乘积最大的子数组
给你一个整数数组 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.乘积为正数的最长数组长度
给你一个整数数组 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.等差数列划分
如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。
- 例如,
[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.最长湍流子数组
给定一个整数数组 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.单词拆分
给你一个字符串 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.环绕字符串中唯⼀的⼦字符串
定义字符串 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代码