
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
23.等差数列划分
题目链接:
题目描述:

题目示例:

解法(动态规划):
算法思路:
1.状态表示:
由于我们的研究对象是「一段连续的区间」,如果我们状态表示定义成[0,i]区间内一共有多少等差数列,那么我们在分析dp[i]的状态转移时,会无从下手,因为我们不清楚前面那么多的「等差数列都在什么位置」。所以说,我们定义的状态表示必须让等差数列「有迹可循」,让状态转移的时候能找到「大部队」。因此,我们可以「固定死等差数列的结尾」,定义下面的状态表示:
dp[i] 表示:必须「以i位置的元素为结尾」的等差数列有多少种。
2.状态转移方程:
我们需要了解一下等差数列的性质:如果abc三个数成等差数列,这时候来了一个d,其中bcd也能构成一个等差数列,那么abcd四个数能够成等差序列吗?答案是:显然的。因为他们之间相邻两个元素之间的差值都是一样的。有了这个理解,我们就可以转而分析我们的状态转移方程了。
对于dp[i]位置的元素nums[i],会与前面的两个元素有下面两种情况:
i.nums[i-2],nums[i- 1],nums[i]三个元素不能构成等差数列:那么以nums[i]为结尾的等差数列就不存在,此时dp[i]=;
ii.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]
3.初始化:
由于需要用到前两个位置的元素,但是前两个位置的元素又无法构成等差数列,因此dp[0] = dp[1] = 0。
4.填表顺序:
毫无疑问是「从左往右」。
5.返回值:
因为我们要的是所有的等差数列的个数,因此需要返回整个 dp表里面的元素之和。
C++算法代码:
cpp
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;
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;
}
else
{
dp[i] = 0;
}
}
int ret = 0;
for(int i = 0; i < n; i++)
{
ret += dp[i];
}
return ret;
}
};
算法总结及流程解析:



24.最长湍流子数组
题目链接:
题目描述:

题目示例:

解法(动态规划):
算法思路:
1.状态表示:
我们先尝试定义状态表示为:
dp[i]表示「以i位置为结尾的最长湍流数组的长度」。
但是,问题来了,如果状态表示这样定义的话,以i位置为结尾的最长湍流数组的长度我们没法从之前的状态推导出来。因为我们不知道前一个最长湍流数组的结尾处是递增的,还是递减的。因
此,我们需要状态表示能表示多一点的信息:要能让我们知道这一个最长湍流数组的结尾是「递
增」的还是「递减」的。
因此需要两个dp表:
f[i]表示:以i位置元素为结尾的所有子数组中,最后呈现「上升状态」下的最长湍流数组的长度;
g[i]表示:以i位置元素为结尾的所有子数组中,最后呈现「下降状态」下的最长湍流数组的长度。
2.状态转移方程:
对于i位置的元素arr[i],有下面两种情况:
i.arr[i]>arr[i-1]:如果i位置的元素比i-1位置的元素大,说明接下来应该去找i -1 位置结尾,并且i-1 位置元素比前一个元素小的序列,那就是g[i-1]。更新 f[i]位置的值:f[i]=g[i-1]+ 1;
ii.arr[i]< arr[i-1]:如果i位置的元素比i-1 位置的元素小,说明接下来应该去找i- 1 位置结尾,并且i-1 位置元素比前一个元素大的序列,那就是f[i-1]。更新 g[i]位置的值:g[i] = f[i-1] + 1 ;
iii.arr[i]==arr[i -1]:不构成湍流数组。
3.初始化:
所有的元素「单独」都能构成一个湍流数组,因此可以将dp表内所有元素初始化为1。
由于用到前面的状态,因此我们循环的时候从第二个位置开始即可。
4.填表顺序:
毫无疑问是「从左往右,两个表一起填」。
5.返回值:
应该返回「两个dp表里面的最大值」,我们可以在填表的时候,顺便更新一个最大值。
C++算法代码:
cpp
class Solution {
public:
int maxTurbulenceSize(vector<int>& arr)
{
int n = arr.size();
if(n == 1)
{
return 1;
}
vector<int> dp(n);
dp[0] = 1;
dp[1] = arr[0] == arr[1] ? 1 : 2;
for(int i = 2; i < n; i++)
{
if(arr[i] > arr[i - 1])
{
dp[i] = arr[i - 1] < arr[i - 2] ? dp[i - 1] + 1 : 2;
}
else if(arr[i] < arr[i - 1])
{
dp[i] = arr[i - 1] > arr[i - 2] ? dp[i - 1] + 1 : 2;
}
else
{
dp[i] = 1;
}
}
int ret = INT_MIN;
for(int i = 0; i < n; i++)
{
ret = max(ret, dp[i]);
}
return ret;
}
};
算法总结及流程解析:


结束语
到此,23.等差数列划分,24.最长湍流子数组 这两道算法题就讲解完了。等差数列划分问题:通过定义dp[i]表示以i结尾的等差子数组数量,利用相邻元素差值关系进行状态转移;最长湍流子数组问题:采用双DP数组分别记录上升和下降状态的最长子数组长度,根据元素大小关系进行状态更新。希望大家能有所收获!