三、动规_子数组系列

server/2025/2/22 18:37:26/

文章目录

  • 子数组系列问题
  • 53. [最大子数组和](https://leetcode.cn/problems/maximum-subarray/description/)
    • 思路
    • 代码
  • 918. [环形子数组的最大和](https://leetcode.cn/problems/maximum-sum-circular-subarray/description/)
    • 思路
    • 代码
  • 152. [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/description/)
    • 思路
    • 代码
  • 1567. [乘积为正数的最长子数组长度](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/description/)
    • 思路
    • 代码
  • 413. [等差数列划分](https://leetcode.cn/problems/arithmetic-slices/description/)
    • 思路
    • 代码
  • 978. [最长湍流子数组](https://leetcode.cn/problems/longest-turbulent-subarray/description/)
    • 思路
    • 代码
  • 139. [单词拆分](https://leetcode.cn/problems/word-break/description/)
    • 思路
    • 代码
  • 467. [环绕字符串中唯一的子字符串](https://leetcode.cn/problems/unique-substrings-in-wraparound-string/description/)
    • 思路
    • 代码

子数组系列问题

dp[i] 表示以第 i 个元素结尾的满足条件的子数组的某种最优值(如最大和、最大长度等)。然后根据 dp[i - 1] 和当前元素 nums[i] 的关系来推导 dp[i] 的值

53. leetcode.cn/problems/maximum-subarray/description/" rel="nofollow">最大子数组和

给你一个整数数组 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 是返回数组中最大元素地址

代码

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. leetcode.cn/problems/maximum-sum-circular-subarray/description/" rel="nofollow">环形子数组的最大和

给定一个长度为 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 &lt;= k1, k2 &lt;= 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,说明原来数组全都是负数

返回跨越首尾的最大子数组的和,不跨越首尾的最大子数组的和两个当中最大值。

代码

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) {// 更新 dpMaxdpMax[i] = max(nums[i], dpMax[i - 1] + nums[i]);// 更新 dpMindpMin[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. leetcode.cn/problems/maximum-product-subarray/description/" rel="nofollow">乘积最大子数组

给你一个整数数组 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 数组中的最大值

代码

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. leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/description/" rel="nofollow">乘积为正数的最长子数组长度

给你一个整数数组 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中最大的值返回

代码

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) {// 若第一个元素为正,以它结尾的乘积为正的子数组长度为 1dp[0] = 1;// 以它结尾的乘积为负的子数组长度为 0_dp[0] = 0;} else if (nums[0] < 0) {// 若第一个元素为负,以它结尾的乘积为正的子数组长度为 0dp[0] = 0;// 以它结尾的乘积为负的子数组长度为 1_dp[0] = 1;} else {// 若第一个元素为 0,以它结尾的乘积为正和为负的子数组长度都为 0dp[0] = 0;_dp[0] = 0;}// 从第二个元素开始遍历数组for (int i = 1; i < n; ++i) {if (nums[i] > 0) {// 如果当前元素为正,正数乘正数还是正数,所以以当前元素结尾的乘积为正的子数组长度// 等于以前一个元素结尾的乘积为正的子数组长度加 1dp[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;// 否则为 0dp[i] = _dp[i - 1] > 0 ? _dp[i - 1] + 1 : 0;} else {// 如果当前元素为 0,任何数乘 0 都为 0,所以以当前元素结尾的乘积为正和为负的子数组长度都为 0dp[i] = 0;_dp[i] = 0;}}// 返回 dp 数组中的最大值,即整个数组中乘积为正数的最长子数组长度return *max_element(dp.begin(), dp.end());}
};

413. leetcode.cn/problems/arithmetic-slices/description/" rel="nofollow">等差数列划分

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

- 例如,`[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 中。

代码

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. leetcode.cn/problems/longest-turbulent-subarray/description/" rel="nofollow">最长湍流子数组

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

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

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

- 若 `i &lt;= k &lt; j` :- 当 `k` 为奇数时, `A[k] &gt; A[k+1]`,且- 当 `k` 为偶数时,`A[k] &lt; A[k+1]`;- **或 **若 `i &lt;= k &lt; j` :- 当 `k` 为偶数时,`A[k] &gt; A[k+1]` ,且- 当 `k` 为奇数时, `A[k] &lt; A[k+1]`。

示例 1:

**输入:**arr = [9,4,2,10,7,8,8,1,9]
**输出:**5
**解释:**arr[1] &gt; arr[2] &lt; arr[3] &gt; arr[4] &lt; 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 中最大值

代码

class Solution {
public:int maxTurbulenceSize(vector<int>& nums) {int n = nums.size();// 如果数组只有一个元素,最长湍流子数组长度就是 1if (n == 1) return 1;// dp 数组用于记录以每个位置结尾的最长湍流子数组的长度vector<int> dp(n);// 初始化 dp[0] 为 1,因为单个元素的子数组长度为 1dp[0] = 1;// 对于 dp[1],如果两个元素不相等,最长湍流子数组长度为 2,否则为 1dp[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] 的基础上加 1dp[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 个元素结尾的最长湍流子数组长度为 2dp[i] = 2;} else {// 如果相邻元素相等,以第 i 个元素结尾的最长湍流子数组长度为 1dp[i] = 1;}// 更新最长湍流子数组的长度ret = max(ret, dp[i]);}return ret;}
};

139. leetcode.cn/problems/word-break/description/" rel="nofollow">单词拆分

给你一个字符串 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 并跳出内层循环。

代码

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] 初始化为 truedp[0] = true;// 遍历字符串 s 的每个位置,从 1 到 nfor (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. leetcode.cn/problems/unique-substrings-in-wraparound-string/description/" rel="nofollow">环绕字符串中唯一的子字符串

定义字符串 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 = 0s[0] = 'a'index = 'a' - 'a' = 0dp[0] = 1hash[0] 初始值为 0,更新后 hash[0] = std::max(0, 1) = 1

    • i = 1s[1] = 'b'index = 'b' - 'a' = 1dp[1] = 2hash[1] 初始值为 0,更新后 hash[1] = std::max(0, 2) = 2

    • i = 2s[2] = 'c'index = 'c' - 'a' = 2dp[2] = 3hash[2] 初始值为 0,更新后 hash[2] = std::max(0, 3) = 3

    • i = 3s[3] = 'z'index = 'z' - 'a' = 25dp[3] = 1hash[25] 初始值为 0,更新后 hash[25] = std::max(0, 1) = 1

    • i = 4s[4] = 'a'index = 'a' - 'a' = 0dp[4] = 2,此时 hash[0] 已经是 1,更新后 hash[0] = std::max(1, 2) = 2

最后将hash中所有值相加就是要求的子串数量

代码

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')) {// 如果连续,以当前字符结尾的连续子串最大长度等于以前一个字符结尾的连续子串最大长度加 1dp[i] = dp[i - 1] + 1;}}// 创建一个无序映射 m,键为字符,值为以该字符结尾的连续子串的最大长度unordered_map<char, int> m;// 遍历字符串 sfor(int i = 0; i < n; ++i) {// 对于每个字符,更新 m 中该字符对应的连续子串最大长度// 取当前记录的值和 dp[i] 中的较大值m[s[i]] = max(m[s[i]], dp[i]);}// 用于存储最终结果,即环绕字符串 s 中唯一的子字符串的数量int ret = 0;// 遍历无序映射 mfor(auto& e : m) {// 将每个字符对应的连续子串最大长度累加到结果中ret += e.second;}// 返回最终结果return ret;}
};

http://www.ppmy.cn/server/169907.html

相关文章

PHP脚本示例

/*** desc 清理产品商品无效数据* param Input $input* php think goods -m Clear -a clearGoods --endTime2020-01-01 -vvv* return void* author 陈龙* date 2024-04-02 9:45*/public function clearGoods(Input $input){$options $input->getOptions();$start_time mic…

如何监控和优化 MySQL 中的慢 SQL

如何监控和优化 MySQL 中的慢 SQL 前言一、什么是慢 SQL&#xff1f;二、如何监控慢 SQL&#xff1f;1. 启用慢查询日志启用方法&#xff1a;日志内容&#xff1a; 2. 使用 mysqldumpslow 分析日志 三、如何分析慢 SQL&#xff1f;1. 使用 EXPLAIN 分析执行计划使用方法&#x…

国产编辑器EverEdit - 如何在EverEdit中创建工程?

1 创建工程 1.1 应用场景 工程是一个文件及文件夹的集合&#xff0c;对于稍微有点规模的项目&#xff0c;一般都会包含多个文件&#xff0c;甚至还会以文件夹的形式进行分层管理多个文件&#xff0c;为了方便的管理这个项目&#xff0c;可以将这些文件和文件夹保存为一个工程。…

Spring Boot 项目开发流程全解析

目录 引言 一、开发环境准备 二、创建项目 三、项目结构 四、开发业务逻辑 1.创建实体类&#xff1a; 2.创建数据访问层&#xff08;DAO&#xff09;&#xff1a; 3.创建服务层&#xff08;Service&#xff09;&#xff1a; 4.创建控制器层&#xff08;Controller&…

Java每日精进·45天挑战·Day20

第二部分&#xff1a;链表旋转 在数据结构中&#xff0c;链表是一种非常基础且重要的数据结构。它允许我们在不需要大量数据移动的情况下&#xff0c;在任意位置插入或删除元素。今天&#xff0c;我们将探讨一个链表相关的有趣问题&#xff1a;如何将链表向右旋转 k 个位置&am…

深入解析 Hydra 库:灵活强大的 Python 配置管理框架

深入解析 Hydra 库&#xff1a;灵活强大的 Python 配置管理框架 在机器学习、深度学习和复杂软件开发项目中&#xff0c;管理和维护大量的配置参数是一项具有挑战性的任务。传统的 argparse、json 或 yaml 方式虽然能管理部分配置&#xff0c;但随着项目规模的增长&#xff0c…

什么是逻辑分析仪?

逻辑分析仪电子工程师的“显微镜” 一、什么是逻辑分析仪&#xff1f; 逻辑分析仪是一种用于测量和分析数字电路信号的电子测试设备。它主要用于观察和记录数字信号的时序关系、逻辑状态以及数据传输情况。与示波器不同&#xff0c;示波器主要用于测量模拟信号的电压和时间特性…

解锁外观模式:Java 编程中的优雅架构之道

系列文章目录 文章目录 一、引言二、外观模式基础&#xff08;一&#xff09;外观模式的定义&#xff08;二&#xff09;外观模式的结构&#xff08;三&#xff09;外观模式的作用 三、外观模式在 Java 中的实现&#xff08;一&#xff09;简单示例&#xff1a;智能家电控制&am…