三、动规_子数组系列


文章目录

子数组系列问题

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 &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,说明原来数组全都是负数

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

代码

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]

  • 从第二个元素开始遍历数组,对于每个元素numsi

    • 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 &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。

  • 当三个数是"大 - 小 - 大"或者"小 - 大 - 小"就dpi = dpi - 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] 表示字符串 s0, 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 来记录以每个字母结尾的最长连续子串的长度,hash0对应'a'hash1对应'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中所有值相加就是要求的子串数量

代码

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;
    }
};
相关推荐
江畔柳前堤7 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore8 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考9 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX10 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面11 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号11 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽11 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI11 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者13 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者13 小时前
【征程6】校准量化中HistogramObserver解析
算法