1.题目链接:环形⼦数组的最⼤和
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:
输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3
示例 2:
输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10
示例 3:
输入:nums = [3,-2,2,-3]
输出:3
解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3
提示:
n == nums.length
1 <= n <= 3 * 10^4
-3 * 10^4 <= nums[i] <= 3 * 10^4
3.问题分析:这道题对于一个数组来说,逻辑上首尾是相连的,求子数组的最大和;直接求肯定是求不出来的,还记得清上篇多状态中有道打家劫舍II也是对于环形数组求解,那道题中一个房子如果偷的话,那么相邻的房子就不能再偷,我们对首尾进行详细分析,对[0, n - 2]和[1, n - 1]区间分别进行操作;这道题和那道题没关系哈,提出来只是让大家回忆一下,接下来就是这道题的思路;说难也不难,但是确实不好太想到,首先会有两种结果1. 结果在数组的内部,包括整个数组;2. 结果在数组⾸尾相连的⼀部分上。整个数组的总和sum是不变的,如果结果在数组⾸尾相连的⼀部分上,那么数组中间就会空出来一部分选不上,中间没选上的数组和就是子数组和的最小值;所以这道题求一个最大子数组和与一个最小子数组和,然后用sum减去最小子数组和与所求最大子数组和作比较,返回最大的那一个即可。
f[i] = max(nums[i], f[i - 1] + nums[i])
;g[i]的分析与f[i]基本相同,可得g[i]的状态转移方程为g[i] = min(nums[i], g[i - 1] + nums[i])
。4.代码如下:
class Solution
{
public:
int maxSubarraySumCircular(vector<int>& nums)
{
int n = nums.size();
vector<int> f(n + 1), g(n + 1);
int sum = 0;
int fmax = INT_MIN; //f数组中的最大值
int gmin = INT_MAX; //g数组中的最小值
for (int i = 1; i <= n; ++i)
{
f[i] = max(f[i - 1] + nums[i - 1], nums[i - 1]);
fmax = max(fmax, f[i]);
g[i] = min(g[i - 1] + nums[i - 1], nums[i - 1]);
gmin = min(gmin, g[i]);
sum += nums[i - 1];
}
gmin = sum - gmin;
return gmin == 0 ? fmax : max(fmax, gmin);
}
};
1.题目链接:添加链接描述
2.题目描述:给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32位 整数。
子数组 是数组的连续子序列。
示例 1:
输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例 2:
输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
提示:
1 <= nums.length <= 2 * 10^4
-10 <= nums[i] <= 10
nums 的任何前缀或后缀的乘积都 保证 是一个 32-位 整数
3.问题分析:如果用一个dp表来表示乘积最大的子数组,就会发现当出现负数时一个dp表是不能表示出来的,负数乘负数可能会是最大的乘积。所以可以用两个dp表来表示,一个表为f:表示前i个元素,子数组乘积的最大值;另一个表为g:表示前i个元素中,子数组乘积的最小值。
4.代码如下:
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)
{
int x = nums[i - 1], y = f[i - 1] * nums[i - 1], z = g[i - 1] * nums[i - 1];
f[i] = max(x, max(y, z));
g[i] = min(x, min(y, z));
ret = max(ret, f[i]);
}
return ret;
}
};
1.题目链接:等差数列划分
2.题目描述:
如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。
例如,[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。
给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组个数。
子数组 是数组中的一个连续序列。
3.算法流程:
即dp[i] = dp[i - 1] + 1(如果是等差数列的话)
。4.代码如下:
class Solution
{
public:
int numberOfArithmeticSlices(vector<int>& nums)
{
int n = nums.size();
vector<int> dp(n);
int ret = 0;
for (int i = 2; i < n; ++i)
{
if ((nums[i] - nums[i-1]) == (nums[i-1] - nums[i-2]))
dp[i] = dp[i-1] + 1;
ret += dp[i];
}
return ret;
}
};
1.题目链接:最长湍流子数组
2.题目描述:给定一个整数数组 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:
输入:arr = [9,4,2,10,7,8,8,1,9]
输出:5
解释:arr[1] > arr[2] < arr[3] > arr[4] < arr[5]
示例 2:
输入:arr = [4,8,12,16]
输出:2
示例 3:
输入:arr = [100]
输出:1
3.问题分析:这道题需要找最长交错的子数组,交错就是 i 位置的比 i - 1 的值要大(小),i + 1位置的比 i 位置要小(大),可以说要求交错的最长子数组。首先想用一个dp表来解决,会发现数组中的值一会比前一个大,一会又比前一个小,用一个dp表表示不了,再用两个dp表来试试,数组中的元素一会大一会小,那么就用f[i]来表示 i 位置的元素比 i - 1 中的元素大,所存的最长子数组;用g[i]来表示 i 位置的元素比 i - 1 中的元素小,所存的最长子数组,这样表示这道题就会很容易求出来。
f[i]来表示 i 位置的元素比 i - 1 中的元素大,所存的最长子数组;用g[i]来表示 i 位置的元素比 i - 1 中的元素小,所存的最长子数组。
f[i] = g[i - 1] + 1
; 2.arr[i] < arr[i - 1] :如果 i 位置的元素⽐ i - 1 位置的元素⼩,说明接下来应该去找 i - 1 位置结尾,并且 i - 1 位置元素⽐前⼀个元素⼤的序列,那就是f[i - 1] 。更新 g[i] 位置的值: g[i] = f[i - 1] + 1
; arr[i] == arr[i - 1] :不构成湍流数组。4.代码如下:
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;
}
};
1.题目链接:单词拆分
2.题目描述:给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true 解释: 返回 true
因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。
示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”] 输出: true 解释: 返回
true 因为 “applepenapple” 可以由 “apple” “pen” “apple” 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false
提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20 s 和 wordDict[i] 仅由小写英文字母组成 wordDict 中的所有字符串互不相同
3.问题分析:要利用字典中出现的单词拼接出 s ,那么就可以认为从s中的0位置拿一个子数组与字典中的元素匹配,然后再向后拿一个子数组匹配,如果s中的每个子数组都可以在字典中找到,那么可以利用字典中出现的单词拼接出 s 。
class Solution
{
public:
bool wordBreak(string s, vector<string>& wordDict)
{
//将字典⾥⾯的单词存在哈希表⾥⾯
unordered_set<string> hash;
for (auto& s : wordDict) hash.insert(s);
int n = s.size();
vector<bool> dp(n + 1);
dp[0] = true; // 保证后续填表是正确的
for (int i = 1; i <= n; i++) // 填 dp[i]
{
for (int j = i; j >= 1; j--) //最后⼀个单词的起始位置
{
if (dp[j - 1] && hash.count(s.substr(j - 1, i - j + 1))) //添加辅助结点,对应的位置为j - 1
{
dp[i] = true;
break;
}
}
}
return dp[n];
}
};
1.题目链接:环绕字符串中唯⼀的子字符串
2.问题描述:
定义字符串 base 为一个 “abcdefghijklmnopqrstuvwxyz” 无限环绕的字符串,所以 base 看起来是这样的:
“…zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd…”.
给你一个字符串 s ,请你统计并返回 s 中有多少 不同非空子串 也在 base 中出现。
示例 1:
输入:s = “a”
输出:1
解释:字符串 s 的子字符串 “a” 在 base 中出现。
示例 2:
输入:s = “cac”
输出:2
解释:字符串 s 有两个子字符串 (“a”, “c”) 在 base 中出现。
示例 3:
输入:s = “zab”
输出:6
解释:字符串 s 有六个子字符串 (“z”, “a”, “b”, “za”, “ab”, and “zab”) 在 base 中出现。
提示:
1 <= s.length <= 10^5
s 由小写英文字母组成
3.问题分析:这道题其实可以说寻找递增子数组的个数
,比如如果只有a到y,(并且元素不重复)用一个dp表,i 位置表示的是以 i 结尾子数组的个数,将每块相连的递增子数组个数相加即将dp表中的元素求和;现在再加一个z
,并且z到a可以看作是连接的,那么就加个if语句将这种情况判断一下即可;然后再加一个条件元素可以重复
,但是题中所求子数组是不能重复,dp表是的是以i为结尾子数组的个数,比如aababc(“a”,“b”,“c”,“ab”,“bc”,“abc”),对于后面的abc来说已经包含前面的ab了,所以只用求出以某个结尾的最大子数组的个数,用一个哈希数组可以来解决,如果s[i]的元素一样,那么就保留dp[i]中的最大值。
即dp[i] = dp[i - 1] + 1(如果是等差数列的话)
。代码如下:
class Solution
{
public:
int findSubstringInWraproundString(string s)
{
int n = s.size();
// 1. 利⽤ dp 求出每个位置结尾的最⻓连续⼦数组的⻓度
vector<int> dp(n, 1);
for (int i = 1; i < n; i++)
if (s[i] - 1 == s[i - 1] || (s[i - 1] == 'z' && s[i] == 'a'))
dp[i] = dp[i - 1] + 1;
// 2. 计算每⼀个字符结尾的最⻓连续⼦数组的⻓度
int hash[26] = { 0 };
for (int i = 0; i < n; i++)
hash[s[i] - 'a'] = max(hash[s[i] - 'a'], dp[i]);
// 3. 将结果累加起来
int sum = 0;
for (auto x : hash) sum += x;
return sum;
}
};