本文涉及知识点
3686. 稳定子序列的数量
给你一个整数数组 nums。
如果一个 子序列 中 不存在连续三个 元素奇偶性相同(仅考虑该子序列内),则称该子序列为稳定子序列 。
请返回所有稳定子序列的数量。
由于结果可能非常大,请将答案对 109 + 7 取余数后返回。
子序列 是一个从数组中通过删除某些元素(或不删除任何元素),并保持剩余元素相对顺序不变的 非空 数组。
示例 1:
输入: nums = [1,3,5]
输出: 6
解释:
稳定子序列为:[1], [3], [5], [1, 3], [1, 5], 和 [3, 5]。
子序列 [1, 3, 5] 不稳定,因为它包含三个连续的奇数。因此答案是 6。
示例 2:
输入: nums = [2,3,4,2]
输出: 14
解释:
唯一一个不稳定子序列是 [2, 4, 2],因为它包含三个连续的偶数。
所有其他子序列都是稳定子序列。因此答案是 14。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 105
动态规划
动态规划的状态表示
dp[n][m1][m2],n表示已经处理了nums长度为n的前缀,m2表示此子序列最后一个数字是否是奇数,m1表示奇偶性相同的最长后缀。 0 ≤ m 1 < 3 , 0 ≤ m 2 < 2 0 \le m1 <3,0\le m2 <2 0≤m1<3,0≤m2<2。
利用滚动向量优化空间,pre=dp[n],cur=dp[n+1]。优化后,空间复杂度:O(1)。
动态规划的填报顺序
枚举前驱状态,和选择。选取当前数字,不选取当前数字。
动态规划的转移方程
处理不选取当前数字: cur =pre。
| 前驱状态m2 | 当前数是否奇数 | 后续状态 |
|---|---|---|
| true | true | cur[m1+1][m2] |
| true | false | cur[1][0] |
| false | true | cur[1][1] |
| false | false | cur[m1+1][m2] |
动态规划的初始值
pre[0][0]=1
动态规划的返回值
∑ p r e \sum pre ∑pre -1。排除空串。
代码
核心代码
cpp
template<long long MOD = 1000000007,class T1 = int, class T2 = long long>
class C1097Int
{
public:
C1097Int(T1 iData = 0) :m_iData(iData% MOD)
{
}
C1097Int(T2 llData) :m_iData(llData% MOD) {
}
C1097Int operator+(const C1097Int& o)const
{
return C1097Int(((T2)m_iData + o.m_iData) % MOD);
}
C1097Int& operator+=(const C1097Int& o)
{
m_iData = ((T2)m_iData + o.m_iData) % MOD;
return *this;
}
C1097Int& operator-=(const C1097Int& o)
{
m_iData = ((T2)MOD + m_iData - o.m_iData) % MOD;
return *this;
}
C1097Int operator-(const C1097Int& o)const
{
return C1097Int(((T2)MOD + m_iData - o.m_iData) % MOD);
}
C1097Int operator*(const C1097Int& o)const
{
return((T2)m_iData * o.m_iData) % MOD;
}
C1097Int& operator*=(const C1097Int& o)
{
m_iData = ((T2)m_iData * o.m_iData) % MOD;
return *this;
}
C1097Int operator/(const C1097Int& o)const
{
return *this * o.PowNegative1();
}
C1097Int& operator/=(const C1097Int& o)
{
*this *= o.PowNegative1();
return *this;
}
bool operator==(const C1097Int& o)const
{
return m_iData == o.m_iData;
}
bool operator<(const C1097Int& o)const
{
return m_iData < o.m_iData;
}
C1097Int pow(T2 n)const
{
C1097Int iRet = (T1)1, iCur = *this;
while (n)
{
if (n & 1)
{
iRet *= iCur;
}
iCur *= iCur;
n >>= 1;
}
return iRet;
}
C1097Int PowNegative1()const
{
return pow(MOD - 2);
}
T1 ToInt()const
{
return ((T2)m_iData + MOD) % MOD;
}
private:
T1 m_iData = 0;;
};
class Solution {
public:
int countStableSubsequences(vector<int>& nums) {
vector<vector<C1097Int<>>> pre(3, vector<C1097Int<>>(2));
pre[0][0] = 1;
for (const auto& n : nums)
{
auto cur = pre;
for (int i = 0;i < 3;i++) {
for (int j = 0;j < 2; j++) {
if (n & 1) {
if (0 == j) {
cur[1][1] += pre[i][j];
}
else {
if (i < 2) {
cur[i + 1][1] += pre[i][j];
}
}
}
else {
if (1 == j) {
cur[1][0] += pre[i][j];
}
else if (i < 2) {
cur[i + 1][0] += pre[i][j];
}
}
}
}
pre.swap(cur);
}
C1097Int<> ans(-1);
for (const auto& v : pre) {
ans += accumulate(v.begin(), v.end(), C1097Int<>(0));
}
return ans.ToInt();
}
};
单元测试
cpp
vector<int> nums;
public:
TEST_METHOD(TestMethod11)
{
nums = { 1, 3, 5 };
auto res = Solution().countStableSubsequences(nums);
AssertEx(6, res);
}
TEST_METHOD(TestMethod12)
{
nums = { 2,3,4,2 };
auto res = Solution().countStableSubsequences(nums);
AssertEx(14, res);
}

扩展阅读
| 我想对大家说的话 |
|---|
| 工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
| 学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
| 有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
| 员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。 |
| 闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
| 如果程序是一条龙,那算法就是他的是睛 |
| 失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法 用**C++**实现。
