动态规划:路径和子数组问题(C++)

动态规划:路径和子数组问题

    • 路径问题
      • 1.不同路径(中等)
      • 2.不同路径II(中等)
      • 3.下降路径最⼩和(中等)
      • 4.地下城游戏(困难)
    • 子数组问题
      • 1.最大子数组和(中等)
      • 2.环形子数组的最大和(中等)
      • 3.乘积最大子数组(中等)
      • 4.乘积为正数的最长子数组(中等)
      • 5.等差数列划分(中等)
      • 6.最长湍流子数组(中等)
      • 7.单词拆分(中等)
      • 8.环绕字符串中唯⼀的子字符串(中等)

路径问题

1.不同路径(中等)

链接:不同路径

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    尝试定义状态表示为到达[m, n]位置的路径数
    在这里插入图片描述

  2. 状态转移方程
    通过上述分析,可知状态转移方程为:
    dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

  3. 初始化
    在这里插入图片描述

  4. 填表顺序
    保证填当前状态时,所需状态已经计算过,从起点出发,填表顺序为从上往下,每一行从左往右

  5. 返回值
    根据状态表示,返回的应该是dp[m][n],即到达终点的路径数。

  • 代码实现
class Solution {
public:int uniquePaths(int m, int n) {//dp[i][j]表示到达该位置的路径vector<vector<int>> dp(m+1, vector<int>(n+1,0));dp[0][1] = 1;for(int i = 1; i <= m; i++){for(int j = 1; j <= n;j++){dp[i][j] = dp[i][j - 1] + dp[i - 1][j];}} return dp[m][n];//时间复杂度:O(N)//空间复杂度:O(N^M)}
};//滚动数组优化
// class Solution {
// public:
//     int uniquePaths(int m, int n) 
//     {
//         vector<int> dp(n + 1);
//         dp[1] = 1;//         for(int i = 1; i <= m; i++)
//         {
//             for(int j = 1; j <= n;j++)
//             {
//                 dp[j] += dp[j-1];
//             }
//         } 
//         return dp[n];
//     }
// };

2.不同路径II(中等)

链接:不同路径II

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    这个题和第一题唯一不同就是加入了障碍物(1),我们只需要进行判断,如果该位置是障碍物就填0,否则依据转移方程填表

  2. 状态转移方程
    通过上述分析,可知状态转移方程为:
    dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

  3. 初始化
    和第一题一样,多开一圈,dp[0][1]或dp[1][0]初始为1。

  4. 填表顺序
    和第一题一样,填表顺序为从上往下,每一行从左往右

  5. 返回值
    根据状态表示,返回的应该是dp[m][n],即到达终点的路径数。

  • 代码实现
class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m = obstacleGrid.size();int n = obstacleGrid[0].size();//dp[i][j]:到达该位置的路径数vector<vector<int>> dp(m + 1,vector<int>(n + 1));dp[0][1] = 1; //dp[1][0] = 1;for(int i = 1; i < m + 1; i++){for(int j = 1; j < n + 1; j++){//多开了一圈,注意下标映射if(obstacleGrid[i - 1][j - 1] == 0){dp[i][j] = dp[i-1][j] + dp[i][j-1];}//vector默认初始0,障碍物对应位置无需处理}}return dp[m][n];//时间复杂度:O(N)//空间复杂度:O(N^2)}
};

3.下降路径最⼩和(中等)

链接:下降路径最⼩和

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    在这里插入图片描述

  2. 状态转移方程
    由前面的分析可知,状态转移方程为:
    dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i - 1][j + 1]}) + matrix[i - 1][j - 1](本身的值)

  3. 初始化
    先全部初始化为极大值,然后第一行初始化为0

  4. 填表顺序
    从上往下,每一行从左往右。

  5. 返回值
    依据状态表示和题目要求,返回最后一行的最小值即可

  • 代码实现
class Solution {
public:int minFallingPathSum(vector<vector<int>>& matrix) {//dp[i][j]表示到这个位置最小路径和int n = matrix.size();vector<vector<int>> dp(n + 1, vector<int>(n + 2, INT_MAX));//初始化第一行for(int j = 0; j < n + 2; j++){dp[0][j] = 0;}for(int i = 1; i < n + 1; i++){for(int j = 1; j < n + 1; j++){dp[i][j] = min({dp[i-1][j-1], dp[i-1][j], dp[i-1][j+1]})+ matrix[i - 1][j - 1]; }}//再遍历一次找最小int ret = dp[n][0];for(auto e : dp[n]){ret = min(ret, e);           }return ret;//时间复杂度:O(N)//空间复杂度:O(N^2)}
};

4.地下城游戏(困难)

链接:地下城游戏

  • 题目描述

在这里插入图片描述

  • 做题步骤
  1. 状态表示

在这里插入图片描述

  1. 状态转移方程
    由前面的分析可知,状态转移方程为:
    dp[i][j] = min(dp[i][j + 1], dp[i + 1][j]) - dungeon[i][j](自身出去的消耗)

  2. 初始化
    先将所有值初始化为极大值,dp[m][n - 1] = dp[m - 1][n] = 1。

  3. 填表顺序
    从下往上,每一行从右向左。

  4. 返回值
    由状态表示可知,返回值为dp[0][0](即[0,0]位置到终点需要的最小生命)

  • 代码实现
// 看点位的状态表示
//从左上到右下:点位表示到这里需要的最小健康点数,点位并不是只受到左边和上面的影响,也要受后面点位的影响(后面点位可能使自己死亡),这种状态表示肯定不能//从右下到左上:点位表示从这里开始到终点所需要的最小健康点数。class Solution {
public:int calculateMinimumHP(vector<vector<int>>& dungeon) {//dp[i][j]表示以这个位置为起点到终点所需要的最小健康点数int m = dungeon.size();int n = dungeon[0].size();vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX));//初始化dp[m][n - 1] = dp[m - 1][n] = 1;for(int i = m - 1; i >= 0; i--){for(int j = n - 1; j >=0; j--){dp[i][j] = min(dp[i][j + 1], dp[i + 1][j]) - dungeon[i][j];//如果dp[i][j]为负数,说明这个位置是奶//直接奶满了,从这里到终点所要的点数1就足够(小于1就死了)dp[i][j] = max(dp[i][j], 1);}}return dp[0][0];//时间复杂度:O(N)//空间复杂度:O(N^2)}
};

子数组问题

1.最大子数组和(中等)

链接:最大子数组和

  • 题目描述

在这里插入图片描述

  • 做题步骤
  1. 状态表示
    这个题目我们可以定义状态表示为以i位置为结尾的子数组的最大和
    因为子数组必须是连续的,所以i位置有两种选择:
    (1)接在以i - 1位置为结尾的子数组后面,即dp[i] = dp[i - 1] + 自身点数
    (2)不接在别人后面(可能dp[i - 1]是负值),就自己一个,即dp[i] = 自身点数
    从两种选择中选择最大的一种,即dp[i] = max(dp[i -1] + 自身点数, 自身点数)

  2. 状态转移方程
    由前面的分析可知,状态转移方程为:
    dp[i] = max(dp[i -1] + 自身点数, 自身点数)

  3. 初始化
    无需初始化。

  4. 填表顺序
    从左往右

  5. 返回值
    无法直接确定最大子数组的结尾位置,可以定义变量ret,一边dp一边更新最大值

  • 代码实现
class Solution {
public:int maxSubArray(vector<int>& nums) {//在原数组上面dp就行//dp[i]:以i位置为结尾的最大子数组和int ret = nums[0];for(int i = 1; i < nums.size(); i++){nums[i] = max(nums[i - 1] + nums[i], nums[i]);//遍历的过程顺便找最大ret = max(ret, nums[i]);      }                return ret;//时间复杂度:O(N)//空间复杂度:O(1)}
};

2.环形子数组的最大和(中等)

链接:环形子数组的最大和

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    这个题目和前一题类似,我们依然可以定义状态表示为以i位置为结尾的最大子数组和

    对于环形问题,最常见的做法就是分情况讨论,分解问题:
    (1)最大子数组不成环,比如[-1,2,3,-1],这个情况做法和前一道题一样

    (2)最大子数组成环,比如[2,1,-3,-2,1],最大子数组成环情况,数组中剩余的连续部分一定是最小子数组
    子数组是连续的,环形可以理解为左右扩张,所有有利于自己的连续部分一定会被吞并,剩下的一定是最小子数组和。
    但数组的总和是不变的,我们只需要用总和减去最小子数组和即可得到成环情况的最大和

  2. 状态转移方程
    不成环最大子数组和用f表来记录,最小子数组和用g表来记录
    状态转移方程为:
    f[i] = max(nums[i], f[i - 1] + nums[i])
    g[i] = min(nums[i], g[i - 1] + nums[i])

  3. 初始化
    为避免填表越界,处理第一个位置,f[0] = g[0] = nums[0]

  4. 填表顺序
    都是从左往右

  5. 返回值
    无法直接确定最大(小)子数组的结尾,所以一边dp一边记录最大(小)值
    (1)f表最大值->fmax
    (2)g表最小值->gmin
    (3)总和->sum
    还有一种特殊情况就是环形数组长度为0(数组中全是负数),这个时候最大值为fmax而不是0,所以返回值为sum == gmin ? fmax : max(fmax, sum - gmin)

  • 代码实现
class Solution {
public:int maxSubarraySumCircular(vector<int>& nums) {int n = nums.size();//一种情况是不需要环形,区间就在数组中//第二种情况是需要环形,数组总大小恒定//非目标区间是连续并且在数组中的,所以最大 = 总 - 非目标区间//比如 5 -3 5,第一种得到5,第二种为 7(总) - (-3) = 10int sum = nums[0];//f[i]:以i位置为结尾的不成环最大子数组和vector<int> f(n);//g[i]:以i位置为结尾的最小子数组和auto g = f;f[0] = g[0] = nums[0];int fmax = nums[0];int gmin = nums[0];for(int i = 1; i < n; i++){f[i] = max(nums[i], f[i - 1] + nums[i]);g[i] = min(nums[i], g[i - 1] + nums[i]);fmax = max(fmax, f[i]);gmin = min(gmin, g[i]);sum += nums[i];}//sum和gmin相同说明里面全是负数,这个时候fmax才是最大,不能为0return sum == gmin ? fmax : max(fmax, sum - gmin);//时间复杂度:O(N)//空间复杂度:O(N)}
};

3.乘积最大子数组(中等)

链接:乘积最大子数组

  • 题目描述
    在这里插入图片描述

  • 做题步骤

这个题目子数组长度最小可以为1,其实所有子数组默认乘了一个1

  1. 状态表示
    依据前面最大子数组和的经验,我们可以定义状态为以i位置为结尾的最大乘积

    但只有这一个状态表示是不够的,负数的加入对乘积影响是巨大的,比如[1,2,3,-1,-2,1],前面按最大和的做法来还没问题,但遇到多个负数就会出问题,这里[1,2,3,-1,-2]可以得到最大乘积12,如果按照最大和的做法只能得到6。

    出现上面情况的原因在于负数的出现使得原来的最大乘积变成了最小乘积,但如果保存最小乘积,当遇到负数时最小乘积就可以变成最大乘积

    综上所述,我们需要同时记录最大和最小乘积,其中最大用f表记录,最小用g表记录。
    (1)x = nums[i](当前位置的值)
    (2)y = f[i - 1](前一个位置的最大乘积)
    (3)z = g[i - 1](前一个位置的最小乘积)

  2. 状态转移方程
    一共就三种情况:
    (1)x:不接在别人后面,子数组长度为1。
    (2)x * y:接在前一个位置最大乘积子数组后面,有可能得到最大(小)乘积。
    (3)y * z:接在前一个位置最小乘积子数组后面,有可能得到最大(小)乘积。

    由此可知状态转移方程为:
    f[i] = max( {x, x * y, x * z} )
    g[i] = min( {x, x * y, x * z} )

  3. 初始化
    当前位置的f、g更新需要前一个位置,第一个位置的最大(小)乘积就是值本身,为了避免第一个位置越界,可以在前面多开一个空间并初始化为1,即f[0] = g[0] = 1,这样不会影响第一个位置。

  4. 填表顺序
    从左往右。

  5. 返回值
    无法直接确定最大子数组的结尾位置,一边dp一边更新最大值

  • 代码实现
class Solution {
public:int maxProduct(vector<int>& nums) {int n  = nums.size();//dp[i]:以i为结尾的最大乘积//f[i]表示最大乘积,g[i]表示最小乘积vector<int> f(n + 1);auto g = f;f[0] = g[0] = 1;//ret变量记录最大乘积int ret = INT_MIN;for(int i = 1; i <= n; i++){int x = nums[i - 1];//现在int y = f[i - 1]; //上个位置的最大乘积int z = g[i - 1]; //上个位置的最小乘积//如果遇到负数的情况,原本最大乘积可能会变成最小,原本最小可能会变成最大f[i] = max({x, x * y, x * z});g[i] = min({x, x * y, x * z});ret = max(ret, f[i]);}return ret;//时间复杂度:O(N)//空间复杂度:O(N)}
};

4.乘积为正数的最长子数组(中等)

链接:乘积为正数的最长子数组

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    这个题目和上一道类似,要考虑负数的加入,因此只有一个状态表示是不够的。
    (1)f表:以i位置为结尾,乘积为正数的最长子数组长度。
    (2)g表:以i位置为结尾,乘积为负数的最长子数组长度。

  2. 状态转移方程
    设当前位置的值为x
    (1)x为负数时,接在前一个位置的后面,原本乘积正数的子数组会变成负数,乘积负数的子数组会变成正数。
    即f[i] = g[i - 1] + 1 和 g[i] = f[i - 1] + 1,但前一个位置结尾的子数组乘积可能无法出现负数(前面都是正数),即g[i - 1] == 0,这个时候f[i]应该也为0。
    故状态转移方程为:
    f[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1
    g[i] = f[i - 1] + 1


    (2)当x为正数时,接在前一个位置的后面,原本乘积正数的子数组还是正数,乘积负数的子数组还是负数。
    (也要考虑前一个位置乘积负数不存在的情况
    故状态转移方程为:
    f[i] = f[i - 1] + 1
    g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1

  3. 初始化
    当前位置的f、g更新需要前一个位置,第一个位置为负数,g[i]为1,f[i]为0,值为正数则相反。为了避免第一个位置越界,可以在前面多开一个空间并初始化为0,即f[0] = g[0] = 0,这样不会影响第一个位置。

  4. 填表顺序
    保证填当前状态时,所需状态已经计算过,填表顺序很明显是从左往右

  5. 返回值
    无法直接确定乘积为正数的最长子数组结尾位置,定义变量ret,一边dp一边更新最大值

  • 代码实现
class Solution {
public:int getMaxLen(vector<int>& nums) {//dp[i] 表示这个位置乘积为负数/正数时的最大长度int n = nums.size();vector<int> f(n + 1); // 正数auto g = f;//负数int ret = -1;for(int i = 1; i < n + 1; i++){//这个数是正数if(nums[i - 1] > 0){f[i] = f[i - 1] + 1;//要考虑前一个位置乘积负数不存在的情况g[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;                }else if(nums[i - 1] < 0)  //负数{f[i] = g[i - 1] == 0 ? 0 : g[i - 1] + 1;g[i] = f[i - 1] + 1;           }//这个数为0,两个状态都不存在,不用处理(本来就是0)ret = max(ret, f[i]);}return ret;//时间复杂度:O(N)//空间复杂度:O(N)}
};

5.等差数列划分(中等)

链接:等差数列划分

  • 题目描述

在这里插入图片描述

  • 做题步骤
  1. 状态表示
    依据前面的经验,我们可以定义状态表示为以i位置为结尾的等差数组个数

  2. 状态转移方程
    以[1,2,3,4]为例子进行分析:
    (1)像1、2这样的位置为结尾数组长度不足3,是一定不能构成等差数组的,即dp[i] = 0。
    (2)像4这样的位置,先看能不能和前面两个元素构成等差数组(满足nums[i] + nums[i - 2] == 2* nums[i - 1]),如果可以的话那4也一定可以接在以3为结尾的等差数组后面,即dp[i] = dp[i - 1] + 1。

    综上所述,状态转移方程为:
    可以和前两个数构成等差数组:dp[i] = dp[i - 1] + 1
    不能和前两个数构造等差数组:dp[i] = 0

  3. 初始化
    无需初始化。

  4. 填表顺序
    保证填当前状态时,所需状态已经计算过,填表顺序很明显是从左往右

  5. 返回值
    题目要求返回所有等差子数组,定义变量sum,一边dp一边累加

  • 代码实现
class Solution {
public:int numberOfArithmeticSlices(vector<int>& nums) {// 1 2 3 4 5//3位置1种   4位置除了拼在3位置可能的后面,还可以抢2 3 来组成//4位置2种   5位置除了拼在4位置可能的后面,还可以抢3 4 来组成//………………对更长的序列也是如此int n = nums.size();//dp[i]:以i位置为结尾的等差数组个数vector<int> dp(n);int sum = 0;for(int i = 2; i < n; i++){//等差数列的性质:num[i] + nums[i - 2] == 2 * num[i - 1]if(nums[i] + nums[i - 2] == 2* nums[i - 1]){dp[i] = dp[i - 1] + 1;}//不满足等差数组为0,vector默认给0,不用处理sum += dp[i];}return sum;//时间复杂度:O(N)//空间复杂度:O(N)}
};

6.最长湍流子数组(中等)

链接:最长湍流子数组

  • 题目描述
    在这里插入图片描述

  • 做题步骤

  1. 状态表示
    依据前面的经验,我们定义状态表示为以i位置为结尾的最长湍流子数组长度

    但只知道i-1位置的最大长度是无法推导出i位置的最大长度的,因为不知道前一个最长湍流子数组结束是处于上升状态(最后的比较符号为’<‘)还是下降状态(最后的比较符号为’>')

    因此可以对状态进行细分:
    (1)f[i]表示以i位置为结尾并处于上升状态(最后的比较符号为’<')的最长湍流子数组长度
    (2)g[i]表示以i位置为结尾并处于下降状态(最后的比较符号为’>')的最长湍流子数组长度

  2. 状态转移方程
    因为湍流子数组比较符号必须在每个相邻元素之间翻转,所以状态转移方程与当前的比较符号相关。
    (1)arr[i-1] < arr[i],现在处于上升状态,需要前置状态处于下降状态的最长湍流子数组长度,即f[i] = g[i - 1] + 1
    (2)arr[i-1] > arr[i],现在处于下降状态,需要前置状态处于上升状态的最长湍流子数组长度,即g[i] = f[i - 1] + 1
    (3)arr[i-1] = arr[i],不能和前面组合,只能自己重新开始,即f[i] = g[i] = 1

  3. 初始化
    推导当前状态需要前一个状态,像第一个位置不能跟在别人后面,两个状态的长度都为1,f[0] = g[0] = 1。因为arr[i-1] = arr[i]时f[i] = g[i] = 1,所以干脆一开始全都初始化为1,就不用单独处理arr[i-1] = arr[i]的情况。

  4. 填表顺序
    保证填当前状态时,所需状态已经计算过,填表顺序很明显是从左往右

  5. 返回值
    无法直接确定最长湍流数组的结尾位置以及结尾是处于上升还是下降状态,所以定义变量ret,一边dp一边更新最大值

  • 代码实现
class Solution {
public:int maxTurbulenceSize(vector<int>& arr) {int n = arr.size();//dp[i]表示以i位置为结尾并且处于上升(下降)状态的最长湍流子数组的长度 vector<int> f(n, 1); //f表示处于上升(<)状态//初始化为1,可以把'=='的情况直接处理了auto g = f; //g表示处于下降(>)状态int ret = 1;for(int i = 1; i < n; i++){if(arr[i-1] < arr[i])f[i] = g[i - 1] + 1;else if(arr[i - 1] > arr[i])g[i] = f[i - 1] + 1;ret = max( {ret, f[i], g[i]} );}return ret;//时间复杂度:O(N)//空间复杂度:O(N)}
};

7.单词拆分(中等)

链接:单词拆分

  • 题目描述

在这里插入图片描述

  • 做题步骤
  1. 状态表示
    依据前面的经验,我们可以定义状态表示为以i位置为结尾的字符串能否由字典中的单词拼出

  2. 状态转移方程
    以s = “leetcode”, wordDict = [“leet”, “code”]为例进行分析:
    (1)先看字符’t’位置(下标3位置),以这个位置为结尾的字符串如果能由字典中的单词拼出,一共有下面几种可能:
    ①"lee"可以由字典中的单词拼出(即dp[2] = true),"t"也可以由字典中的单词拼出。
    ②"le"可以由字典中的单词拼出(即dp[1] = true),"et"也可以由字典中的单词拼出。
    ③"l"可以由单词中的单词拼出(即dp[0] = true),"eet"也可以由字典中的单词拼出。
    ④再往前就没有了,"leet"可以由字典中的单词拼出。

    其它位置的分析也和上述一致,将当前字符串分成[0, j - 1]区间和[j, i]区间,从 0 ~ i 枚举 j ,只要 dp[j - 1] = true并且后面部分的子串 s.substr(j, i - j + 1) 能够在字典中找到,那么 dp[i] = true 。

  3. 初始化
    处理④这样的情况,可以多加一个虚拟节点并初始化true(dp[0] = true),可以理解为空串能在字典中找到。同时为了方便处理下标的映射关系,我们可以在字符串s前面加一个占位符(s = ’ ’ + s),这样就不用考虑下标的映射了。

  4. 填表顺序
    保证填当前状态时,所需状态已经计算过,填表顺序很明显是从左往右

  5. 返回值
    根据状态表示,假设字符串长度为n,返回的应该是dp[n]

  6. 优化
    为了方便查询字符串是否在字典中,可以把字典的单词存储到哈希表中

  • 代码实现
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; // 保证后续填表是正确的s = ' ' + s; // 使原始字符串的下标统⼀ for(int i = 1; i <= n; i++) {for(int j = i; j >= 1; j--) //最后⼀个单词的起始位置{if(dp[j - 1] && hash.count(s.substr(j, i - j + 1))){dp[i] = true;break; //已经确定为真就可以跳出这一层循环了}}}return dp[n];//时间复杂度:O(N^2)//空间复杂度:O(N)}
};

8.环绕字符串中唯⼀的子字符串(中等)

链接:环绕字符串中唯⼀的子字符串

  • 题目描述

在这里插入图片描述

  • 做题步骤
  1. 状态表示
    依据前面的经验,我们定义状态表示为以i位置为结尾并且在base中出现的子字符串个数

  2. 状态转移方程
    这个题目中的base数组是按照abcd……zabcd这样的顺序来的,要注意base成环。
    以i位置为结尾并在base中出现的子字符串有下面三种可能:
    (1)不拼在别人后面,就单独自己一个,该字符串一定会在base中出现。
    (2)拼在别人后面,并且满足s[i] = s[i - 1] + 1(即满足abcd递增)
    (3)拼在别人后面,并且满足s[i] == ‘a’ && s[i - 1] == ‘z’(刚好成环)

    综上所述,状态转移方程为:
    ①满足(2)(3)中任意一个,dp[i] = dp[i - 1] + 1(这个1是自己,dp[i - 1]是拼在别人后面)
    ②不满足(2)(3),dp[i] = 1

  3. 初始化
    每个位置最少也有自己单独一个的情况,所以全都初始化为1

  4. 填表顺序
    保证填当前状态时,所需状态已经计算过,填表顺序很明显是从左往右

  5. 返回值
    这个题目最需要注意的就是对dp表数据的处理,因为dp表中可能有大量重复的数据,比如"abcdcd"中’d’字符出现了两次,"cd"和"d"这两个字符串在dp表中是多次记录了的,我们需要对dp表数据进行去重。

    每个字符都对应了固定的ASCLL码,因此可以可以创建⼀个大小为 26 的数组,遍历dp表,对于出现多次的字符,只需保留以该字符为结尾的最大dp值

    去重完成后再进行累加就可以得到结果。

  • 代码实现
class Solution {
public:int findSubstringInWraproundString(string s) {//base是abcd……连续的//s[i]表示现在位置//所以字串要存在要么s[i] == s[i - 1] + 1//要么(s[i - 1] == 'z' && a[i]=='a') int n = s.size();//dp[i]:以i位置为结尾并且在base中出现的子字符串数vector<int> dp(n, 1);for(int i = 1; i < n; i++){if(s[i] == s[i-1] + 1 || (s[i-1] == 'z' && s[i] == 'a')){dp[i] = dp[i - 1] + 1; // 这个1是自己}}int hash[26] = {0};//遍历一次,统计对应字符最大的出现次数//"abcdd"这样的后面那个d的1是无效的,要去掉for(int i = 0; i < n; i++){int index = s[i] - 'a';hash[index] = max(hash[index], dp[i]);}//最后累加int sum = 0;for(auto e : hash){sum += e;}return sum;//时间复杂度:O(N)//空间复杂度:O(N)}
};

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

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

相关文章

一篇文章教会你SpringMVC

目录 1.什么是SpringMVC 2.SpringMVC工作流程 3.SpringMVC核心组件 4.SpringMVC的配置流程 4.1导入POM依赖 4.2在WEB-INF下添加springmvc-servlet.xml(spring-mvc.xml) 4.3 修改web.xml 创建一个Controller用来存放web层的方法和内容 创建一个前端页面用来做测试展示 前言…

04 Linux补充|C/C++

目录 Linux补充 C语⾔ C语言中puts和printf的区别&#xff1f; Linux补充 (1)ubuntu安装ssh服务端openssh-server命令&#xff1a; ubuntu安装后默认只有ssh客户端&#xff0c;只能去连其它ssh服务器&#xff1b;其它客户端想要连接这个ubuntu系统&#xff0c;需要安装部署…

进制转换(二进制、八进制、十六进制、十进制)

一、进制表示 二进制&#xff1a;每一位只有两种符号表示 -> 0,1 例如 (101011)₂&#xff0c;也可写作101011B&#xff0c;其中B是Binary英文的缩写。八进制&#xff1a; 每一位有8种符号表示(0~7)&#xff0c;例如(1652)₈&#xff0c;也可写作1652O&#xff0c;其中O是O…

STL常用容器 (C++核心基础教程之STL容器详解)String的API

在C的标准模板库&#xff08;STL&#xff09;中&#xff0c;有多种容器可供使用。以下是一些常见的容器类型&#xff1a; 序列容器&#xff08;Sequential Containers&#xff09;&#xff1a; std::vector&#xff1a;动态数组&#xff0c;支持快速随机访问。 std::list&…

CS420 课程笔记 P7 - 虚拟内存 多级指针寻址

文章目录 IntroPointersMemory leaksPointer pathPointer scanningExample! Intro 上节课我们学习了静态地址&#xff0c;这节课我们将着手关注动态地址&#xff0c;我们需要了解一个叫做指针的东西 Pointers 简单地说&#xff0c;指针是对象之间的单向连接 Pointers are co…

vue集成mars3d后,basemaps加不上去

首先&#xff1a; <template> <div id"centerDiv" class"mapcontainer"> <mars-map :url"configUrl" οnlοad"onMapload" /> </div> </template> <script> import MarsMap from ../component…

C到C++的升级

C和C的关系 C继承了所有C语言的特性&#xff1b;C在C的基础上提供了更多的语法和特性&#xff0c;C语言去除了一些C语言的不好的特性。C的设计目标是运行效率与开发效率的统一。 变化一&#xff1a;所有变量都可以在使用时定义 C中更强调语言的实用性&#xff0c;所有的变量…

解决centos离线安装cmake找不到OpenSSL问题

安装方法&#xff1a;见另外一篇文章 https://blog.csdn.net/zhongxj183/article/details/118488629 按照文章下载了离线gcc 和OpenSSL&#xff0c;以及在cmake官网下载了最新版 cmake-3.27.4.tar.gz 顺利安装gcc 和OpenSSL 但执行编译cmake时&#xff0c;报错找不到OpenSSL…

【python】读取.dat格式文件

import binascii# 打开二进制文件以只读二进制模式 with open(EXCEL/文件.dat, rb) as file:binary_data file.read()print(binary_data)# 将二进制数据转换为十六进制字符串 hex_data binascii.hexlify(binary_data).decode(utf-8) # binary_data 现在包含了文件的二进制内容…

计算机图形学线性代数相关概念

Transformation&#xff08;2D-Model&#xff09; Scale(缩放) [ x ′ y ′ ] [ s 0 0 s ] [ x y ] (等比例缩放) \left[ \begin{matrix} x \\ y \end{matrix} \right] \left[ \begin{matrix} s & 0 \\ 0 & s \end{matrix} \right] \left[ \begin{matrix} x \\ y \en…

页面页脚部分CSS分享

先看效果&#xff1a; CSS部分&#xff1a;&#xff08;查看更多&#xff09; <style>body {display: grid;grid-template-rows: 1fr 10rem auto;grid-template-areas: "main" "." "footer";overflow-x: hidden;background: #F5F7FA;min…

Qt+C++自建网页浏览器-Chrome blink最新内核基础上搭建-改进版本

程序示例精选 QtC自建网页浏览器-Chrome blink最新内核基础上搭建-改进版本 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<QtC自建网页浏览器-Chrome blink最新内核基础上搭建-改进版…

linux并发服务器 —— linux网络编程(七)

网络结构模式 C/S结构 - 客户机/服务器&#xff1b;采用两层结构&#xff0c;服务器负责数据的管理&#xff0c;客户机负责完成与用户的交互&#xff1b;C/S结构中&#xff0c;服务器 - 后台服务&#xff0c;客户机 - 前台功能&#xff1b; 优点 1. 充分发挥客户端PC处理能力…

机器学习笔记之最优化理论与方法(六)无约束优化问题——最优性条件

机器学习笔记之最优化理论与方法——无约束优化问题[最优性条件] 引言无约束优化问题无约束优化问题最优解的定义 无约束优化问题的最优性条件无约束优化问题的充要条件无约束优化问题的必要条件无约束优化问题的充分条件 引言 本节将介绍无约束优化问题&#xff0c;主要介绍无…

DDR2 IP核调式记录2

本文相对简单&#xff0c;只供自己看看就行。从其它的博客找了个代码&#xff0c;然后记录下仿真波形。 1. 功能 直接使用quartus生成的DDR2 IP核&#xff0c;然后实现循环 -->写入burst长度的数据后读出。 代码数据的传输是32位&#xff0c;实际使用了两片IC。因此IP核也是…

51单片机热水器温度控制系统仿真设计( proteus仿真+程序+原理图+报告+讲解视频)

51单片机热水器温度控制系统仿真设计 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单 &&下载链接 51单片机热水器温度控制系统仿真设计( proteus仿真程序原理图报告讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#x…

【图解RabbitMQ-2】图解JMS规范与AMQP协议是什么

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;CSDN实力新星&#xff0c;后端开发两年经验&#xff0c;曾担任甲方技术代表&#xff0c;业余独自创办智源恩创网络科技工作室。会点点Java相关技术栈、帆软报表、低代码平台快速开…

FreeRTOS操作系统中,断言输出 Error:..\..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c,766 原因

分析&#xff1a;Error:..\..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c,766 出现这个原因表示&#xff0c;你现在系统某个中断的优先级高于FreeRTOS可管理的优先级范围&#xff0c;一旦你这个中断触发&#xff0c;断言的信息即你串口就会输出这个条语句&#xff08;前提你串口…

这3个教学难题,你中招了吗?

在当今教育领域&#xff0c;提高教育质量和学生学习成果是学校和教育机构的首要任务之一。教育管理者、教师和政策制定者都在寻求创新的方法来监督和改进教育过程。 在线巡课系统应运而生&#xff0c;成为教育界的一项重要工具&#xff0c;旨在帮助学校管理者更好地理解教育实践…

Python爬虫:下载小红书无水印图片、视频

该代码只提供学习使用&#xff0c;该项目是基于https://github.com/JoeanAmier/XHS_Downloader的小改动 1.下载项目 git clone https://github.com/zhouayi/XHS_Downloader.git2.找到需要下载的文章的ID 写入main.py中 3.下载 python main.py最近很火的莲花楼为例<嘿嘿…