文章目录
- 子数组系列问题
- [53. [最大子数组和](https://leetcode.cn/problems/maximum-subarray/description/)](#53. 最大子数组和)
- [918. [环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/description/)](#918. 环形子数组的最大和)
- [152. [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/description/)](#152. 乘积最大子数组)
- [1567. [乘积为正数的最长子数组长度](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/description/)](#1567. 乘积为正数的最长子数组长度)
- [413. [等差数列划分](https://leetcode.cn/problems/arithmetic-slices/description/)](#413. 等差数列划分)
- [978. [最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/description/)](#978. 最长湍流子数组)
- [139. [单词拆分](https://leetcode.cn/problems/word-break/description/)](#139. 单词拆分)
- [467. [环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/description/)](#467. 环绕字符串中唯一的子字符串)
子数组系列问题
dp[i]
表示以第 i
个元素结尾的满足条件的子数组的某种最优值(如最大和、最大长度等)。然后根据 dp[i - 1]
和当前元素 nums[i]
的关系来推导 dp[i]
的值
53. 最大子数组和
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
**子数组 **是数组中的一个连续部分。
示例 1:
**输入:**nums = [-2,1,-3,4,-1,2,1,-5,4]
**输出:**6
**解释:**连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
**输入:**nums = [1]
**输出:**1
示例 3:
**输入:**nums = [5,4,-1,7,8]
**输出:**23
思路
dp[i]
表示以第 i
个元素结尾的连续子数组的最大和。
- 情况一:子数组只包含当前元素
nums[i]
如果前面的子数组和为负数,那么加上前面的子数组会使和变小,不如只取当前元素本身。此时dp[i] = nums[i]
。 - 情况二:子数组包含前面的元素和当前元素
nums[i]
如果前面以第i - 1
个元素结尾的连续子数组和为正数,那么加上当前元素会使和更大。此时dp[i] = dp[i - 1] + nums[i]
。
dp[i]
之后,最大子数组和就是 dp
数组中的最大值,函数 max_element 是返回数组中最大元素地址
代码
c++
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 0);
dp[0] = nums[0];
for(int i = 1; i<n; ++i){
dp[i] = max(nums[i], dp[i-1]+nums[i]);
}
return *max_element(dp.begin(), dp.end());
}
};
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:
**输入:**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
思路
-
dpMax[i]
表示以第i
个元素结尾的非环形最大子数组和。状态转移方程为dpMax[i] = max(nums[i], dpMax[i - 1] + nums[i])
。 -
dpMin[i]
表示以第i
个元素结尾的非环形最小子数组和。状态转移方程为dpMin[i] = min(nums[i], dpMin[i - 1] + nums[i])
。
数组的总和 = 跨越首尾的最大子数组 + 补集的和。让跨越首尾的子数组和最大,就需要让补集的和最小,补集就是不跨越首尾的最小子数组的和。
如果跨越首尾的子数组的和为0,说明原来数组全都是负数
返回跨越首尾的最大子数组的和,不跨越首尾的最大子数组的和两个当中最大值。
代码
c++
class Solution {
public:
int maxSubarraySumCircular(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
// 初始化 dpMax 和 dpMin 数组
vector<int> dpMax(n, 0);
vector<int> dpMin(n, 0);
// 初始化边界条件
dpMax[0] = nums[0];
dpMin[0] = nums[0];
// 计算数组总和
int totalSum = nums[0];
// 动态规划过程
for (int i = 1; i < n; ++i) {
// 更新 dpMax
dpMax[i] = max(nums[i], dpMax[i - 1] + nums[i]);
// 更新 dpMin
dpMin[i] = min(nums[i], dpMin[i - 1] + nums[i]);
// 累加数组元素得到总和
totalSum += nums[i];
}
// 计算跨越首尾的最大子数组和
int ret = totalSum - *min_element(dpMin.begin(), dpMin.end());
// 处理数组元素全为负数的情况
if(ret == 0)return *max_element(dpMax.begin(), dpMax.end());
//返回环中最大子数组的和,非环中最大子数组的和两者最大值
return max(ret, *max_element(dpMax.begin(), dpMax.end()));
}
};
152. 乘积最大子数组
给你一个整数数组 nums
,请你找出数组中乘积最大的非空连续 子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
示例 1:
**输入:** nums = [2,3,-2,4]
**输出:** `6`
**解释:** 子数组 [2,3] 有最大乘积 6。
示例 2:
**输入:** nums = [-2,0,-1]
**输出:** 0
**解释:** 结果不能为 2, 因为 [-2,-1] 不是子数组。
思路
处理乘积问题时,由于负数的存在,一个负数可能会把当前的最小乘积变成最大乘积,所以我们需要同时记录以每个位置结尾的最大乘积和最小乘积。
maxDp[i]
表示以第 i
个元素结尾的连续子数组的最大乘积,minDp[i]
表示以第 i
个元素结尾的连续子数组的最小乘积。
- 当,只有
nums[i]
这一个元素时,此时的乘积就是nums[i]
本身。 - 当
nums[i]
是正数 ,将nums[i]
乘以前面以第i - 1
个元素结尾的连续子数组的最大乘积maxDp[i - 1]
,得到的结果可能会是一个更大的乘积 - 当
nums[i]
是负数,将其乘以前面以第i - 1
个元素结尾的连续子数组的最小乘积minDp[i - 1]
(这个最小乘积可能是负数),得到的结果可能会变成一个更大的正数
返回的是maxDp
数组中的最大值
代码
c++
class Solution {
public:
int maxProduct(vector<int>& nums) {
int n = nums.size();
vector<int> maxdp(n, 0);
vector<int> mindp(n, 0);
maxdp[0] = nums[0];
mindp[0] = nums[0];
for(int i = 1; i<n; ++i){
maxdp[i] = max(max(nums[i], maxdp[i-1]*nums[i]), mindp[i-1]*nums[i]);
mindp[i] = min(min(nums[i], maxdp[i-1]*nums[i]), mindp[i-1]*nums[i]);
}
return *max_element(maxdp.begin(), maxdp.end());
}
};
1567. 乘积为正数的最长子数组长度
给你一个整数数组 nums
,请你求出乘积为正数的最长子数组的长度。
一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。
请你返回乘积为正数的最长子数组长度。
示例 1:
**输入:**nums = [1,-2,-3,4]
**输出:**4
**解释:**数组本身乘积就是正数,值为 24 。
示例 2:
**输入:**nums = [0,1,-2,-3,-4]
**输出:**3
**解释:**最长乘积为正数的子数组为 [1,-2,-3] ,乘积为 6 。
注意,我们不能把 0 也包括到子数组中,因为这样乘积为 0 ,不是正数。
示例 3:
**输入:**nums = [-1,-2,-3,0,1]
**输出:**2
**解释:**乘积为正数的最长子数组是 [-1,-2] 或者 [-2,-3] 。
思路
-
定义
dp[i]
表示以第i
个元素结尾的子数组乘积为正数的最长长度。 -
定义
_dp[i]
表示以第i
个元素结尾的子数组乘积为负数的最长长度。 -
对数组的第一个元素进行单独处理,根据其正负性和是否为 0 来初始化
dp[0]
和_dp[0]
。 -
从第二个元素开始遍历数组,对于每个元素nums[i]
- 若
nums[i] > 0
,正数乘正数结果为正,正数乘负数结果为负。dp[i]
等于dp[i - 1] + 1
,如果_dp[i - 1] > 0
,说明存在以第i - 1
个元素结尾的乘积为负数的子数组,那么将当前正数nums[i]
加入这个子数组后,以第i
个元素结尾的乘积依然为负,且长度在原来的基础上加 1;如果_dp[i - 1] == 0
,说明不存在以第i - 1
个元素结尾的乘积为负数的子数组,那么以第i
个元素结尾的乘积为负数的子数组长度也为 0。 - 若
nums[i] < 0
,正数乘负数结果为负,负数乘负数结果为正。_dp[i]
等于dp[i - 1] + 1
,如果_dp[i - 1] > 0
,说明存在以第i - 1
个元素结尾的乘积为负数的子数组,那么将当前负数nums[i]
加入这个子数组后,负负得正,以第i
个元素结尾的乘积变为正数,且长度在原来的基础上加 1;如果_dp[i - 1] == 0
,说明不存在以第i - 1
个元素结尾的乘积为负数的子数组,那么以第i
个元素结尾的乘积为正数的子数组长度也为 0。 - 若
nums[i] == 0
,任何数乘 0 都为 0,所以dp[i]
和_dp[i]
都为 0。
- 若
找出dp中最大的值返回
代码
c++
class Solution {
public:
int getMaxLen(vector<int>& nums) {
int n = nums.size();
// dp[i] 表示以第 i 个元素结尾的子数组乘积为正数的最长长度
vector<int> dp(n, 0);
// _dp[i] 表示以第 i 个元素结尾的子数组乘积为负数的最长长度
vector<int> _dp(n, 0);
// 处理第一个元素
if (nums[0] > 0) {
// 若第一个元素为正,以它结尾的乘积为正的子数组长度为 1
dp[0] = 1;
// 以它结尾的乘积为负的子数组长度为 0
_dp[0] = 0;
} else if (nums[0] < 0) {
// 若第一个元素为负,以它结尾的乘积为正的子数组长度为 0
dp[0] = 0;
// 以它结尾的乘积为负的子数组长度为 1
_dp[0] = 1;
} else {
// 若第一个元素为 0,以它结尾的乘积为正和为负的子数组长度都为 0
dp[0] = 0;
_dp[0] = 0;
}
// 从第二个元素开始遍历数组
for (int i = 1; i < n; ++i) {
if (nums[i] > 0) {
// 如果当前元素为正,正数乘正数还是正数,所以以当前元素结尾的乘积为正的子数组长度
// 等于以前一个元素结尾的乘积为正的子数组长度加 1
dp[i] = dp[i - 1] + 1;
// 如果前一个元素结尾的乘积为负的子数组长度大于 0,正数乘负数还是负数,
// 则以当前元素结尾的乘积为负的子数组长度等于以前一个元素结尾的乘积为负的子数组长度加 1;
// 否则为 0
_dp[i] = _dp[i - 1] > 0 ? _dp[i - 1] + 1 : 0;
} else if (nums[i] < 0) {
// 如果当前元素为负,正数乘负数为负数,所以以当前元素结尾的乘积为负的子数组长度
// 等于以前一个元素结尾的乘积为正的子数组长度加 1
_dp[i] = dp[i - 1] + 1;
// 如果前一个元素结尾的乘积为负的子数组长度大于 0,负数乘负数为正数,
// 则以当前元素结尾的乘积为正的子数组长度等于以前一个元素结尾的乘积为负的子数组长度加 1;
// 否则为 0
dp[i] = _dp[i - 1] > 0 ? _dp[i - 1] + 1 : 0;
} else {
// 如果当前元素为 0,任何数乘 0 都为 0,所以以当前元素结尾的乘积为正和为负的子数组长度都为 0
dp[i] = 0;
_dp[i] = 0;
}
}
// 返回 dp 数组中的最大值,即整个数组中乘积为正数的最长子数组长度
return *max_element(dp.begin(), dp.end());
}
};
413. 等差数列划分
如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。
- 例如,`[1,3,5,7,9]`、`[7,7,7,7]` 和 `[3,-1,-5,-9]` 都是等差数列。
给你一个整数数组 nums
,返回数组 nums
中所有为等差数组的 子数组 个数。
子数组 是数组中的一个连续序列。
示例 1:
**输入:**nums = [1,2,3,4]
**输出:**3
**解释:**nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。
示例 2:
**输入:**nums = [1]
**输出:**0
思路
核心思路是通过记录以每个元素结尾的算术切片数量,逐步累加得到总的算术切片数量。
-
dp[i]
表示以第i
个元素结尾的算术切片的数量,初始时都为 0。 -
定义变量
ret
用于存储总的算术切片数量,初始值为 0。 -
从数组的第三个元素(索引为 2)开始遍历到数组末尾。对于每个位置
i
,检查nums[i] - nums[i - 1]
是否等于nums[i - 1] - nums[i - 2]
,若相等,则说明从nums[i - 2]
到nums[i]
构成一个算术切片。 -
当构成算术切片时,以第
i
个元素结尾的算术切片数量dp[i]
等于以第i - 1
个元素结尾的算术切片数量dp[i - 1]
加 1。这是因为在以第i - 1
个元素结尾的所有算术切片后面加上当前元素nums[i]
,都能形成新的算术切片,同时再加上由nums[i - 2]
、nums[i - 1]
和nums[i]
组成的新算术切片。 -
每次找到以第
i
个元素结尾的算术切片后,将dp[i]
的值累加到ret
中。
代码
c++
class Solution {
public:
// 该函数用于计算数组中算术切片(等差数列子数组)的数量
int numberOfArithmeticSlices(vector<int>& nums) {
// 获取数组的长度
int n = nums.size();
// 创建一个长度为 n 的数组 dp,dp[i] 表示以第 i 个元素结尾的算术切片的数量
vector<int> dp(n, 0);
// 用于存储总的算术切片数量
int ret = 0;
// 从数组的第三个元素开始遍历,因为算术切片至少需要三个元素
for(int i = 2; i < n; ++i) {
// 判断是否构成算术切片,即当前元素与前一个元素的差值等于前一个元素与前前一个元素的差值
if(nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2]) {
// 如果构成算术切片,以第 i 个元素结尾的算术切片数量等于以第 i - 1 个元素结尾的算术切片数量加 1
// 这是因为在以第 i - 1 个元素结尾的算术切片后面加上当前元素,又能形成新的算术切片
dp[i] = dp[i - 1] + 1;
// 将以第 i 个元素结尾的算术切片数量累加到总的算术切片数量中
ret += dp[i];
}
}
// 返回总的算术切片数量
return ret;
}
};
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:
**输入:**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
思路
分析题目特点是,相邻元素之间呈现"大 - 小 - 大"或者"小 - 大 - 小"模式
dp[i]
表示以第 i
个元素结尾的最长湍流子数组的长度
-
dp[0]
初始化为 1,因为单个元素本身构成的子数组长度就是 1。 -
对于
dp[1]
,如果nums[1]
不等于nums[0]
,说明这两个元素可以构成一个长度为 2 的湍流子数组,dp[1]
赋值为 2;否则,它们只能构成长度为 1 的子数组,dp[1]
赋值为 1。 -
当三个数是"大 - 小 - 大"或者"小 - 大 - 小"就dp[i] = dp[i - 1] + 1,例如,数组
[2, 4, 3]
-
当三个元素只满足"大 - 小 - 大"或者"小 - 大 - 小"中的一半时,例如,数组
[2, 2, 3]
,只有2, 3满足,此时dp[i]
= 2 -
当三个元素只满足"大 - 小 - 大"或者"小 - 大 - 小"只有一个满足,例如,数组
[2, 2, 2]
,只有一个2满足,此时dp[i]
= 1
最后返回 dp 中最大值
代码
c++
class Solution {
public:
int maxTurbulenceSize(vector<int>& nums) {
int n = nums.size();
// 如果数组只有一个元素,最长湍流子数组长度就是 1
if (n == 1) return 1;
// dp 数组用于记录以每个位置结尾的最长湍流子数组的长度
vector<int> dp(n);
// 初始化 dp[0] 为 1,因为单个元素的子数组长度为 1
dp[0] = 1;
// 对于 dp[1],如果两个元素不相等,最长湍流子数组长度为 2,否则为 1
dp[1] = (nums[1] != nums[0]) ? 2 : 1;
// 如果数组只有两个元素,直接返回 dp[1]
if (n == 2) return dp[1];
// 用于记录最长湍流子数组的长度
int ret = 0;
// 从第 2 个元素开始遍历数组
for (int i = 2; i < n; ++i) {
// 判断是否满足湍流条件
if ((nums[i] > nums[i - 1] && nums[i - 2] > nums[i - 1]) || (nums[i] < nums[i - 1] && nums[i - 2] < nums[i - 1])) {
// 如果满足湍流条件,以第 i 个元素结尾的最长湍流子数组长度在 dp[i - 1] 的基础上加 1
dp[i] = dp[i - 1] + 1;
} else if ((nums[i] > nums[i - 1] && nums[i - 2] <= nums[i - 1]) || (nums[i] < nums[i - 1] && nums[i - 2] >= nums[i - 1])) {
// 如果不满足湍流条件,但相邻元素有大小变化,以第 i 个元素结尾的最长湍流子数组长度为 2
dp[i] = 2;
} else {
// 如果相邻元素相等,以第 i 个元素结尾的最长湍流子数组长度为 1
dp[i] = 1;
}
// 更新最长湍流子数组的长度
ret = max(ret, dp[i]);
}
return ret;
}
};
139. 单词拆分
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 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
思路
通过记录字符串 s
的每个前缀是否可以由 wordDict
中的单词组成,逐步推导整个字符串是否满足条件。将 wordDict
中的所有单词存入 unordered_set
中
-
dp[i]
表示字符串s
的 [0, i] 字符是否可以由wordDict
中的单词组成,长度为n + 1
-
dp[0]
初始化为true
,因为空字符串可以认为是可以由字典中的单词组成的。 -
外层循环遍历字符串
s
的每个位置i
从 1 到n
,表示考虑字符串s
的前i
个字符。 -
内层循环遍历
j
从 0 到i - 1
,尝试将前i
个字符拆分为前j
个字符和从第j + 1
个字符到第i
个字符这两部分。 -
如果前
j
个字符可以由字典中的单词组成(即dp[j]
为true
),并且从第j + 1
个字符到第i
个字符组成的子串在字典中(即set.find(s.substr(j, i - j)) != set.end()
),则说明前i
个字符也可以由字典中的单词组成,将dp[i]
设为true
并跳出内层循环。
代码
c++
class Solution {
public:
// 该函数用于判断字符串 s 是否可以由 wordDict 中的单词组成
bool wordBreak(string s, vector<string>& wordDict) {
// 获取字符串 s 的长度
int n = s.size();
// 将 wordDict 中的单词存储到 unordered_set 中,方便快速查找某个单词是否存在
unordered_set<string> set(wordDict.begin(), wordDict.end());
// 创建一个长度为 n + 1 的布尔型数组 dp
// dp[i] 表示字符串 s 的前 i 个字符是否可以由 wordDict 中的单词组成
vector<bool> dp(n + 1, false);
// 空字符串可以由 wordDict 中的单词组成,所以 dp[0] 初始化为 true
dp[0] = true;
// 遍历字符串 s 的每个位置,从 1 到 n
for (int i = 1; i <= n; ++i) {
// 尝试从 0 到 i - 1 的每个位置 j 进行分割
for (int j = 0; j < i; ++j) {
// 如果前 j 个字符可以由 wordDict 中的单词组成(即 dp[j] 为 true)
// 并且从第 j 个位置开始到第 i 个位置的子串存在于 wordDict 中
if (dp[j] && set.find(s.substr(j, i - j)) != set.end()) {
// 则说明前 i 个字符也可以由 wordDict 中的单词组成
dp[i] = true;
// 一旦找到一种分割方式满足条件,就不需要再继续尝试其他分割方式
break;
}
}
}
// 返回 dp[n],即整个字符串 s 是否可以由 wordDict 中的单词组成
return dp[n];
}
};
467. 环绕字符串中唯一的子字符串
定义字符串 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 中出现。
思路
题目是把abc...xyz绕成了一圈的环,然后判断 s 中有多少子串是环的子串。
dp[i]
表示以字符串 s
中第 i
个字符结尾的连续子串的长度。例如:"abc"是3,"za"是2
-
初始化dp都是1,代表每个字母单独都可以是子串
-
之后循环判断相邻两个字符是否连续,也要判断"za"这种特殊情况
-
用
hash
来记录以每个字母结尾的最长连续子串的长度,hash[0]对应
'a',
hash[1]对应
'b' 以此类推。遍历字符串 s ,s[i] - 'a'
计算出它在字母表中的索引index
,如果s[i]
是'a'
,那么index
就是 0 -
遍历过程中,可能会多次遇到同一个字母,每次遇到时
dp[i]
可能不同,我们只关心以该字母结尾的最长连续子串长度,所以使用std::max
函数来更新hash[index]
,确保hash[index]
始终保存的是以该字母结尾的最长连续子串长度。 -
例如,s = "abcza"
,前面已经计算出
dp数组为
[1, 2, 3, 1, 2]。-
当
i = 0
,s[0] = 'a'
,index = 'a' - 'a' = 0
,dp[0] = 1
,hash[0]
初始值为 0,更新后hash[0] = std::max(0, 1) = 1
。 -
当
i = 1
,s[1] = 'b'
,index = 'b' - 'a' = 1
,dp[1] = 2
,hash[1]
初始值为 0,更新后hash[1] = std::max(0, 2) = 2
。 -
当
i = 2
,s[2] = 'c'
,index = 'c' - 'a' = 2
,dp[2] = 3
,hash[2]
初始值为 0,更新后hash[2] = std::max(0, 3) = 3
。 -
当
i = 3
,s[3] = 'z'
,index = 'z' - 'a' = 25
,dp[3] = 1
,hash[25]
初始值为 0,更新后hash[25] = std::max(0, 1) = 1
。 -
当
i = 4
,s[4] = 'a'
,index = 'a' - 'a' = 0
,dp[4] = 2
,此时hash[0]
已经是 1,更新后hash[0] = std::max(1, 2) = 2
。
-
最后将hash中所有值相加就是要求的子串数量
代码
c++
class Solution {
public:
// 该函数用于计算环绕字符串 s 中唯一的子字符串的数量
int findSubstringInWraproundString(string s) {
// 获取字符串 s 的长度
int n = s.size();
// 创建一个长度为 n 的动态规划数组 dp,初始值都设为 1
// dp[i] 表示以 s[i] 结尾的连续子串的最大长度
vector<int>dp(n, 1);
// 遍历字符串 s,从第二个字符开始
for(int i = 1; i < n; ++i) {
// 判断当前字符和前一个字符在环绕字符串中是否连续
// 连续的情况包括普通连续(如 'a' 和 'b')和环绕连续(如 'z' 和 'a')
if(s[i - 1] + 1 == s[i] || (s[i - 1] == 'z' && s[i] == 'a')) {
// 如果连续,以当前字符结尾的连续子串最大长度等于以前一个字符结尾的连续子串最大长度加 1
dp[i] = dp[i - 1] + 1;
}
}
// 创建一个无序映射 m,键为字符,值为以该字符结尾的连续子串的最大长度
unordered_map<char, int> m;
// 遍历字符串 s
for(int i = 0; i < n; ++i) {
// 对于每个字符,更新 m 中该字符对应的连续子串最大长度
// 取当前记录的值和 dp[i] 中的较大值
m[s[i]] = max(m[s[i]], dp[i]);
}
// 用于存储最终结果,即环绕字符串 s 中唯一的子字符串的数量
int ret = 0;
// 遍历无序映射 m
for(auto& e : m) {
// 将每个字符对应的连续子串最大长度累加到结果中
ret += e.second;
}
// 返回最终结果
return ret;
}
};