【算法专题】动态规划之子数组和子串系列

动态规划4.0

  • 动态规划 - - - 子数组、子串系列(数组中连续的一段)
    • 1. 最大子数组和
    • 2. 环形子数组的最大和
    • 3. 乘积最大子数组
    • 4. 乘积为正数的最长子数组长度
    • 5. 等差数列划分
    • 6. 最长湍流子数组
    • 7. 单词拆分
    • 8. 环绕字符串中唯一的子字符串

动态规划 - - - 子数组、子串系列(数组中连续的一段)

1. 最大子数组和

题目链接 -> Leetcode -53.最大子数组和

Leetcode -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

提示:

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

思路:

  1. 状态表示:这里我们选择比较常用的⽅式,以「某个位置为结尾」,结合「题目要求」,定义一个状态表示:dp[i] 表示:以 i 位置元素为结尾的「所有子数组」中和的最大和。

  2. 状态转移方程:dp[i] 的所有可能可以分为以下两种:

  • 子数组的长度为 1 :此时 dp[i] = nums[i] ;
  • 子数组的长度大于 1 :此时 dp[i] 应该等于 以 i - 1 做结尾的「所有子数组」中和的最大值再加上 nums[i] ,也就是 dp[i - 1] + nums[i] 。

由于我们要的是「最大值」,因此应该是两种情况下的最大值,因此可得转移大程:dp[i] = max(nums[i], dp[i - 1] + nums[i]) 。

  1. 返回值:状态表示为「以 i 为结尾的所有子数组」的最大值,但是最大子数组和的结尾我们是不确定的。因此我们需要返回整个 dp 表中的最大值。

代码如下:

		class Solution {public:int maxSubArray(vector<int>& nums){// dp[i] 表⽰:以 i 位置元素为结尾的「所有⼦数组」中和的最⼤和int n = nums.size();vector<int> dp(n);dp[0] = nums[0];for (int i = 1; i < n; i++)dp[i] = max(dp[i - 1] + nums[i], nums[i]);int ret = INT_MIN;for (int i = 0; i < n; i++) ret = max(ret, dp[i]);// 返回 dp 数组中的最大值即可return ret;}};

2. 环形子数组的最大和

题目链接 -> Leetcode -918.环形子数组的最大和

Leetcode -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

提示:

  • n == nums.length
  • 1 <= n <= 3 * 10^4
  • -3 * 10^4 <= nums[i] <= 3 * 10^4​​​​​​​

思路:本题与「最大子数组和」的区别在于,考虑问题的时候不仅要分析「数组内的连续区域」,还要考虑「数组⾸尾相连」的⼀部分。结果的可能情况分为以下两种:

  • 结果在数组的内部,包括整个数组;
  • 结果在数组首尾相连的一部分上。

其中,对于第一种情况,我们仅需按照「最大子数组和」的求法就可以得到结果,记为 fmax 。对于第二种情况,我们可以分析一下:

  • 如果数组首尾相连的一部分是最大的数组和,那么数组中间就会空出来一部分;
  • 因为数组的总和 sum 是不变的,那么中间连续的一部分的和一定是最小的;

因此,我们就可以得出一个结论,对于第二种情况的最大和,应该等于 sum - gmin ,其中 gmin 表示数组内的「最小子数组和」。两种情况下的最大值,就是我们要的结果。

但是,由于数组内有可能全部都是负数,第一种情况下的结果是数组内的最大值(是个负数),第二种情况下的 gmin == sum ,求的得结果就会是 0 。若直接求两者的最大值,就会是 0 。但是实际的结果应该是数组内的最大值。对于这种情况,我们需要特殊判断一下。

**剩下的步骤就是求「最大子数组和」和 「最小子数组和」了,由于上题已经讲过思路,这里就不再讲了,「最小子数组和」的思路和「最大子数组和」也是类似的。 **

代码如下:

		class Solution {public:// 求最大子数组之和int maxSum(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;}// 求最小子数组之和int minSum(vector<int>& nums){int n = nums.size();vector<int> dp(n + 1);int ret = INT_MAX;for(int i = 1; i <= n; i++){dp[i] = min(dp[i - 1] + nums[i - 1], nums[i - 1]);ret = min(ret, dp[i]);}return ret;}int maxSubarraySumCircular(vector<int>& nums) {int sum = 0;// 求数组总和for(int i = 0; i < nums.size(); i++) sum += nums[i];// 求出最大子数组总和在数组内部的情况int _sum = maxSum(nums);// 用数组总和减去最小子数组之和即为最大子数组总和int circularSum = sum - minSum(nums);// 我们最终要返回上面两种情况的最大值// 特判 sum - minSum(nums) 是否等于 0,即原数组中是否全是负数return circularSum == 0? _sum : max(_sum, circularSum);}};

3. 乘积最大子数组

题目链接 -> Leetcode -152.乘积最大子数组

Leetcode -152.乘积最大子数组

题目:给你一个整数数组 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 - 位 整数

思路:

由于正负号的存在,我们很容易就可以得到,如果只用一个状态表示 dp[i] 的值是不正确的。因为 dp[i - 1] 的信息并不能让我们得到 dp[i] 的正确值。比如数组 [-2, 5, -2] ,用上述状态转移得到的 dp数组为 [-2, 5, -2] ,最大乘积为 5 。但是实际上的最大乘积应该是所有数相乘,结果为 20 。

究其原因,就是因为我们在求 dp[2] 的时候,因为 nums[2] 是一个负数,因此我们需要的是「 i - 1 位置结尾的最小的乘积 (-10) 」,这样一个负数乘以「最小值」,才会得到真实的最大值。

因此,我们不仅需要一个「乘积最大值的 dp 表」,还需要⼀个「乘积最小值的 dp 表」。

  1. 状态表示:
  • f[i] 表示:以 i 结尾的所有子数组的最大乘积;
  • g[i] 表示:以 i 结尾的所有子数组的最小乘积;
  1. 状态转移方程:遍历每一个位置的时候,我们要同步更新两个 dp 数组的值。
  • 对于 f[i] ,也就是「以 i 为结尾的所有子数组的最大乘积」,对于所有子数组,可以分为下面三种形式:
    i. 子数组的长度为 1 ,也就是 nums[i] ;
    ii. 子数组的长度大于 1 ,但 nums[i] > 0 ,此时需要的是 i - 1 为结尾的所有子数组的最大乘积 f[i - 1] ,再乘上 nums[i] ,也就是 nums[i] * f[i - 1] ;
    iii. ⼦数组的长度大于 1 ,但 nums[i] < 0 ,此时需要的是 i - 1 为结尾的所有子数组的最小乘积 g[i - 1] ,再乘上 nums[i] ,也就是 nums[i] * g[i - 1] ;

如果 nums[i] = 0 ,所有⼦数组的乘积均为 0 ,三种情况其实都包含了;综上所述, f[i] = max(nums[i], max(nums[i] * f[i - 1], nums[i] * g[i - 1]) )。

  • 对于 g[i] ,也就是「以 i 为结尾的所有子数组的最小乘积」,对于所有子数组,可以分为下面三种形式:
    i. 子数组的长度为 1 ,也就是 nums[i] ;
    ii. 子数组的长度大于 1 ,但 nums[i] > 0 ,此时需要的是 i - 1 为结尾的所有子数组的最小乘积 g[i - 1] ,再乘上 nums[i] ,也就是 nums[i] * g[i - 1] ;
    iii. 子数组的长度大于 1 ,但 nums[i] < 0 ,此时需要的是 i - 1 为结尾的所有子数组的最大乘积 f[i - 1] ,再乘上 nums[i] ,也就是 nums[i] * f[i - 1] ;

如果 nums[i] = 0 ,所有子数组的乘积均为 0 ,三种情况其实都包含了;综上所述, g[i] = min(nums[i], min(nums[i] * f[i - 1], nums[i] * g[i - 1])) 。

3. 返回值:返回 f 表中的最大值;

代码如下:

		class Solution {public:int maxProduct(vector<int>& nums){int n = nums.size();vector<int> f(n + 1), g(n + 1); // f[i] 存到i位置的最大值,g[i]存到i位置的最小值 f[0] = 1, g[0] = 1;int ret = INT_MIN;for (int i = 1; i <= n; i++){// 正数if (nums[i - 1] > 0){f[i] = max(f[i - 1] * nums[i - 1], nums[i - 1]);g[i] = min(g[i - 1] * nums[i - 1], nums[i - 1]);}// 负数else{f[i] = max(g[i - 1] * nums[i - 1], nums[i - 1]);g[i] = min(f[i - 1] * nums[i - 1], nums[i - 1]);}// 取最大值ret = max(ret, f[i]);}return ret;}};

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

题目链接 -> Leetcode -1567.乘积为正数的最长子数组长度

Leetcode -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] 。

提示:

  • 1 <= nums.length <= 10 ^ 5
  • -10 ^ 9 <= nums[i] <= 10 ^ 9

思路:本题的分析方法与上题的类似,所以在这不再作分析,可以参考代码中的注释。

代码如下:

		class Solution {public:int getMaxLen(vector<int>& nums) {int n = nums.size();// g[i] 存放以 i 位置为结尾中,乘积为负数的最长子数组长度// f[i] 存放以 i 位置为结尾中,乘积为正数的最长子数组长度vector<int> f(n + 1), g(n + 1);int ret = INT_MIN;for(int i = 1; i <= n; i++){// nums[i - 1] == 0 的情况可以忽略,因为0不是正数if(nums[i - 1] > 0){f[i] = f[i - 1] + 1;// 当 nums[i - 1] 大于 0,如果 g[i - 1] 等于 0,nums[i - 1] 在 g[i] 中无效,所以为 0g[i] = g[i - 1] == 0? 0: g[i - 1] + 1;}else if(nums[i - 1] < 0){// 当 nums[i - 1] 小于 0,如果 g[i - 1] 等于 0,nums[i - 1] 在 f[i] 中无效,所以为 0f[i] = g[i - 1] == 0? 0 : g[i - 1] + 1;g[i] = f[i - 1] + 1;}ret = max(ret, f[i]);}return ret;}};

5. 等差数列划分

题目链接 -> Leetcode -413.等差数列划分

Leetcode -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

提示:

  • 1 <= nums.length <= 5000
  • -1000 <= nums[i] <= 1000
  1. 状态表示:
    由于我们的研究对象是「一段连续的区间」,如果我们状态表示定义成 [0, i] 区间内一共有多少等差数列,那么我们在分析 dp[i] 的状态转移时,会无从下手,因为我们不清楚前面那么多的「等差数列都在什么位置」。所以说,我们定义的状态表示必须让等差数列「有迹可循」,让状态转移的时候能找到「大部队」。因此,我们可以「固定死等差数列的结尾」,定义下面的状态表示:
  • dp[i] 表示必须「以 i 位置的元素为结尾」的等差数列有多少种;
  1. 状态转移方程:我们需要了解一下等差数列的性质:如果 a b c 三个数成等差数列,这时候来了⼀个 d ,其中 b c d 也能构成一个等差数列,那么 a b c d 四个数能够成等差序列吗?答案是:显然的。因为他们之间相邻两个元素之间的差值都是一样的。有了这个理解,我们就可以转而分析我们的状态转移方程了。

对于 dp[i] 位置的元素 nums[i] ,会与前⾯的两个元素有下⾯两种情况:

  • nums[i - 2], nums[i - 1], nums[i] 三个元素不能构成等差数列:那么以
    nums[i] 为结尾的等差数列就不存在,此时 dp[i] = 0 ;
  • nums[i - 2], nums[i - 1], nums[i] 三个元素可以构成等差数列:那么以 nums[i - 1] 为结尾的所有等差数列后面填上一个 nums[i] 也是一个等差数列,此时dp[i] = dp[i - 1] 。但是,因为 nums[i - 2], nums[i - 1], nums[i] 三者又能构成一个新的等差数列,因此要在之前的基础上再添上一个等差数列,于是dp[i] = dp[i - 1] + 1;

综上所述:状态转移方程为:

  • 当: nums[i - 2] + nums[i] != 2 * nums[i - 1] 时, dp[i] = 0;
  • 当: nums[i - 2] + nums[i] == 2 * nums[i - 1] 时, dp[i] = 1 + dp[i - 1]
  1. 返回值:因为我们要的是所有的等差数列的个数,因此需要返回整个 dp 表里面的元素之和;

代码如下:

		class Solution {public:int numberOfArithmeticSlices(vector<int>& nums) {int n = nums.size();if(n == 1 || n == 2) return 0;vector<int> dp(n);dp[0] = dp[1] = 0;int ans = 0;// dp[i] 表示「以 i 位置的元素为结尾」的等差数列有多少种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;}ans += dp[i];}return ans;}};

6. 最长湍流子数组

题目链接 -> Leetcode -978.最长湍流子数组

Leetcode -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

提示:

  • 1 <= arr.length <= 4 * 10^4
  • 0 <= arr[i] <= 10^9

思路:

  1. 状态表示:我们先尝试定义状态表示为:dp[i] 表示「以 i 位置为结尾的最长湍流数组的长度」。但是,问题来了,如果状态表示这样定义的话,以 i 位置为结尾的最长湍流数组的长度我们没法从之前的状态推导出来。因为我们不知道前一个最长湍流数组的结尾处是递增的,还是递减的。因此,我们需要状态表示能表示多一点的信息:要能让我们知道这一个最长湍流数组的结尾是「递增」的还是「递减」的;

因此需要两个 dp 表:

  • f[i] 表示:以 i 位置元素为结尾的所有子数组中,最后呈现「上升状态」下的最长湍流数组的长度;
  • g[i] 表示:以 i 位置元素为结尾的所有子数组中,最后呈现「下降状态」下的最长湍流数组的长度;
  1. 状态转移方程:对于 i 位置的元素 arr[i] ,有下面两种情况:
  • arr[i] > arr[i - 1] :如果 i 位置的元素比 i - 1 位置的元素大,说明接下来应该去找 i -1 位置结尾,并且 i - 1 位置元素比前一个元素小的序列,那就是 g[i - 1] 。更新 f[i] 位置的值: f[i] = g[i - 1] + 1 ;
  • 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] :不构成湍流数组;
  1. 返回值:应该返回「两个 dp 表里面的最大值」,我们可以在填表的时候,顺便更新⼀个最大值;

代码如下:

		class Solution {public:int maxTurbulenceSize(vector<int>& arr) {int n = arr.size();if(n == 1) return 1;// f[i] 表⽰:以 i 位置元素为结尾的所有⼦数组中,最后呈现「上升状态」下的最长湍流数组的长度;// g[i] 表⽰:以 i 位置元素为结尾的所有⼦数组中,最后呈现「下降状态」下的最长湍流数组的长度vector<int> f(n, 1), g(n, 1);int ans = INT_MIN;for(int i = 1; i < n; i++){f[i] = arr[i - 1] < arr[i]? g[i - 1] + 1 : 1;g[i] = arr[i - 1] > arr[i]? f[i - 1] + 1 : 1;ans = max(ans, max(f[i], g[i]));}return ans;}};

7. 单词拆分

题目链接 -> Leetcode -139.单词拆分

Leetcode -139.单词拆分

题目:给你一个字符串 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 中的所有字符串 互不相同

思路:

  1. 状态表示:dp[i] 表示: [0, i] 区间内的字符串,能否被字典中的单词拼接而成;
  2. 状态转移方程:对于 dp[i] ,为了确定当前的字符串能否由字典里面的单词构成,根据最后一个单词的起始位置 j ,我们可以将其分解为前后两部分:
  • 前面一部分 [0, j - 1] 区间的字符串;
  • 后面一部分 [j, i] 区间的字符串。

其中前面部分我们可以在 dp[j - 1] 中找到答案,后面部分的子串可以在字典里面找到。因此,我们得出一个结论:当我们在从 0 ~ i 枚举 j 的时候,只要 dp[j - 1] = true;并且后面部分的子串 s.substr(j, i - j + 1) 能够在字典中找到,那么 dp[i] = true ;

  1. 返回值:由「状态表示」可得:返回 dp[n] 位置的布尔值;

代码如下:

		class Solution {public:bool wordBreak(string s, vector<string>& wordDict) {// 去重 + 插入方便寻找unordered_set<string> hash;for(const auto& str : wordDict) hash.insert(str);// dp[i] 表示: [0, i] 区间内的字符串,能否被字典中的单词拼接⽽成int n = s.size();vector<bool> dp(n + 1); // 不给值默认初始化为 falsedp[0] = true; // 为了不影响后面的填表s = ' ' + s; // 使字符串下标统一 +1for(int i = 1; i <= n; i++){for(int j = i; j >= 1; j--){// j 寻找一个单词的起始位置if(dp[j - 1] && hash.count(s.substr(j, i - j + 1))){dp[i] = true;break;}}}return dp[n];}};

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

题目链接 -> Leetcode -467.环绕字符串中唯一的子字符串

Leetcode -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 中出现。

提示:

  • 1 <= s.length <= 10^5
  • s 由小写英文字母组成

思路:

  1. 状态表示:dp[i] 表示:以 i 位置的元素为结尾的所有子串里面,有多少个在 base 中出现过;
  2. 状态转移方程:对于 dp[i] ,我们可以根据子串的「长度」划分为两类:
  • 子串的长度等于 1 :此时这一个字符会出现在 base 中;
  • 子串的长度大于 1 :如果 i 位置的字符和 i - 1 位置上的字符组合后,出现在 base中的话,那么 dp[i - 1] 里面的所有子串后面填上一个 s[i] 依旧在 base 中出现。因此 dp[i] = dp[i - 1] 。

综上, dp[i] = 1 + dp[i - 1] ,其中 dp[i - 1] 是否加上需要先做一下判断;

  1. 返回值:这里不能直接返回 dp 表里面的和,因为会有重复的结果。在返回之前,我们需要先「去重」:
  • 相同字符结尾的 dp 值,我们仅需保留「最大」的即可,其余 dp 值对应的子串都可以在最大的里面找到;
  • 可以创建一个大小为 26 的数组,统计所有字符结尾的最大 dp 值。

最后返回「数组中所有元素的和」即可;

代码如下:

		class Solution {public:int findSubstringInWraproundString(string s) {// dp[i] 表⽰:以 i 位置的元素为结尾的所有⼦串⾥⾯,有多少个在 base 中出现过int n = s.size();vector<int> dp(n + 1, 1);s = ' ' + s;//  利⽤ dp 求出每个位置结尾的最⻓连续⼦数组的⻓度for(int i = 1; i <= n; i++){// 判断相邻if(s[i - 1] + 1 == s[i] || (s[i - 1] == 'z'&& 'a' == s[i])){dp[i] = dp[i - 1] + 1;}}// 去重,以相同字符结尾的字符串,我们取 dp 值较大的那一个即可int hash[26] = {0}, ans = 0;for(int i = 1; i <= n; i++) hash[s[i] - 'a'] = max(dp[i], hash[s[i] - 'a']);// 返回数组中的总和for(int i = 0; i < 26; i++) ans += hash[i];return ans;}};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/648218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024-01-25(ElasticSearch)

附上&#xff1a;ElasticSearch从入门到精通&#xff0c;史上最全&#xff08;持续更新&#xff0c;未完待续&#xff0c;每天一点点&#xff09;_elasticsearch从入门到精通,史上最全-CSDN博客 1.ES中存储不是重要的&#xff0c;搜索查询功能才是ES的核心 2.ES提供了基于JSO…

如何在外远程访问家中本地威联通QNAP NAS

文章目录 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 前言 购入威联通NAS后&#xff0c;很多用户对于如何在外在公网环境下的远程访问威联通NAS…

【Python爬虫入门到精通】小白也能看懂的知识要点与学习路线

文章目录 1. 写在前面2. 爬虫行业情况3. 学习路线 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣的朋友可以关…

记一次 .NET某工控自动化系统 崩溃分析

一&#xff1a;背景 1. 讲故事 前些天微信上有位朋友找到我&#xff0c;说他的程序偶发崩溃&#xff0c;分析了个把星期也没找到问题&#xff0c;耗费了不少人力物力&#xff0c;让我能不能帮他看一下&#xff0c;给我申请了经费&#xff0c;哈哈&#xff0c;遇到这样的朋友就…

Python之数据可视化基础

目录 一 JSON数据格式转换 二 pyecharts模块 三 Pyecharts入门 四 数据可视化之疫情折线图 一 JSON数据格式转换 什么是JSON? JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式。它以易于阅读和编写的方式来表示结构化数据。JSO…

机器学习的精髓-梯度下降算法

目 1. 梯度下降算法2. 梯度下降求解3. 总结 1. 梯度下降算法 梯度下降算法是一种优化算法&#xff0c;用于最小化函数的数值方法。它通过沿着函数梯度的反方向来更新参数&#xff0c;以逐步减小函数值。这一过程重复进行直到达到收敛条件。梯度下降算法有多种变体&#xff0c;…

利用Maven获取jar包

我有一个习惯&#xff0c;就是程序不在线依赖网络的任何包。以前用C#时候虽然用Nuget找包&#xff0c;但是添加引用后又马上把Nuget引用删了&#xff0c;再把Nuget下载的dll拷贝到工程再引用dll。 这样做的好处是&#xff1a; 1.别人得到程序代码可以直接编译&#xff0c;不用…

《WebKit 技术内幕》学习之十四(2):调式机制

2 实践——基础和性能调试 Chromium开发者工具基本上沿用了Web Inspector的功能&#xff0c;所以这一节主要以该开发者工具作为介绍的对象&#xff0c;一起了解开发者工具提供的功能和一些基本的用法&#xff0c;有些用法其实在之前已经介绍过&#xff0c;这里可能为了系统性考…

数据类型(下)

数据类型&#xff08;下&#xff09; 1.集合&#xff08;set&#xff09;1.1 定义1.2 独有功能1.3 公共功能1.4 转换1.5 其他1.5.1 集合的存储原理1.5.2 元素必须可哈希1.5.3 查找速度特别快1.5.4 对比和嵌套 练习题 强插&#xff1a;None类型2.字典&#xff08;dict)2.1 定义2…

银行数据仓库体系实践(6)--调度系统

调度系统是数据仓库的重要组成部分&#xff0c;也是每个银行或公司一个基础软件或服务&#xff0c;需要在全行或全公司层面进行规划&#xff0c;在全行层面统一调度工具和规范&#xff0c;由于数据类系统调度作业较多&#xff0c;交易类系统批量优先级高&#xff0c;为不互相影…

基于ssm+vue在线考试系统

摘要 在线考试系统是一种利用现代技术手段实现的教育评估工具&#xff0c;它为学生提供了更灵活、便捷的考试方式&#xff0c;同时为教育机构提供了高效管理和评估学生学业水平的手段。在这个背景下&#xff0c;基于SSM&#xff08;SpringSpringMVCMyBatis&#xff09;框架和Vu…

【OCC学习23】使用Draw探索OCC API 【完结】

对于OCC应用开发者来说&#xff0c;OCC的文档虽然不错&#xff0c;但针对具体的需求找到合适的API还是得不断摸索。我发现看Draw的代码是探索OCC API使用的最佳路径。掌握根据Draw命令查找对应代码就能高效找到解决方案。所以这是本系列的最后一篇分享了&#xff0c;个人感觉OC…

重塑网络安全格局:零信任安全架构的崛起与革新

零信任安全架构是一种现代安全模式&#xff0c;其设计原则是“绝不信任&#xff0c;始终验证”。它要求所有设备和用户&#xff0c;无论他们是在组织网络内部还是外部&#xff0c;都必须经过身份验证、授权和定期验证&#xff0c;才能被授予访问权限。简而言之&#xff0c;“零…

Dockerfile里ADD * 保留原来的目录结构

1、问题 给新模块写Dockerfile&#xff0c;很多静态资源分散在各个目录&#xff0c;于是Dockerfile里我直接一句&#xff1a; ADD ./* /dest/镜像出来后&#xff0c;启动容器&#xff0c;进入容器种后发现&#xff1a;文件拷贝成功&#xff0c;但原来的目录结构都不在了&…

【网站项目】基于SSM的251国外摇滚乐队交流和周边售卖系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

五大自然语言处理技术里程碑浅析

自然语言处理的发展 随着深度学习和大数据技术的进步&#xff0c;自然语言处理取得了显著的进步。人们正在研究如何使计算机更好地理解和生成人类语言&#xff0c;以及如何应用NLP技术改善搜索引擎、语音助手、机器翻译等领域。 而自然语言处理的研究与业界发展中具有五大里程碑…

用javadoc生成springboot的文档

概述&#xff1a;生成 Spring Boot 项目的 JavaDoc 文档与生成普通的 Java 项目类似。 目录 第一步&#xff1a;创建一个springboot项目 第二步&#xff1a;编写pom文件 第三步&#xff1a;运行 Maven 命令生成 JavaDoc 第四步&#xff1a;查看结果 第一步&#xff1a;创建…

AG32VF407 AGRV2K 串口printf调试输出

视频讲解 [AG32VF407]国产MCUFPGA 串口printf调试输出及演示 原理图 测试代码 新建一个platformio工程&#xff0c;复制如下文件到测试工程目录下 E:\tech\AGM-AG32VF\sdk-release\AgRV_pio\platforms\AgRV\boards\agrv2k_407\board.asf E:\tech\AGM-AG32VF\sdk-release\AgRV_…

MySQL排序优化

排序优化 对于order by关键字进行优化前&#xff0c;首先大家要先知道索引不仅用于检索还用于排序 MySQL支持两种方式的排序&#xff0c;index和filesort&#xff0c;index效率高&#xff0c;可以根据索引本身来完成排序&#xff0c;filesort效率较低 最好在进行explain进行分析…

RCD负载箱的未来发展趋势和创新技术有哪些?

随着科技的不断发展&#xff0c;RCD负载箱作为电力系统中的重要设备&#xff0c;其未来发展趋势和创新技术也将不断涌现。以下是一些可能的发展趋势和创新技术&#xff1a; 1. 智能化&#xff1a;未来的RCD负载箱将更加智能化&#xff0c;能够实现远程监控、故障诊断和自动调节…