2026-02-15:稳定子序列的数量。用go语言,给定一个整数数组 nums。我们把从原数组中按原有顺序挑出至少一个元素构成的序列称为“子序列”。若某个子序列中任意连续的三个元素并非同为奇数或同为偶数(即不会出现三个相邻元素奇偶性都相同的情况),则称该子序列为“稳定”。请计算所有稳定子序列的数量,并对 1000000007 取模后返回结果。
1 <= nums.length <= 100000。
1 <= nums[i] <= 100000。
输入: nums = [1,3,5]。
输出: 6。
解释:
稳定子序列为:[1], [3], [5], [1, 3], [1, 5], 和 [3, 5]。
子序列 [1, 3, 5] 不稳定,因为它包含三个连续的奇数。因此答案是 6。
题目来自力扣3686。
整体思路(高层):
把每个数只看奇偶性(0 表示偶,1 表示奇),用动态规划在线统计截至当前处理位置为止的“稳定子序列”的数量。为避免子序列中出现三个连续同奇偶性的元素,维护关于“结尾一段相同奇偶性长度”的状态,只允许结尾相同奇偶性长度为 1 或 2 的子序列存在,任何会产生长度为 3 的扩展都被禁止。
状态定义(关键):
- 维护一个 2×2 的计数矩阵 f,其中第一维是结尾元素的奇偶性 p(0 或 1),第二维表示“结尾连续同奇偶数的长度类别”:
- f[p][0]:当前已统计的、以奇偶性 p 结尾且结尾那段连续同奇偶性的长度为 1 的稳定子序列数量(即最后一个元素与前一个元素奇偶性不同,或子序列长度为1)。
- f[p][1]:当前已统计的、以奇偶性 p 结尾且结尾那段连续同奇偶性的长度为 2 的稳定子序列数量(即最后两元素奇偶性为 p 且相邻相同,但前面不能再接同 p,因为那会变成 3 个连续同奇偶性)。
初始化:
- 所有计数 f[p][k] 从 0 开始(还没处理任何元素时没有非空子序列)。
对数组按顺序处理每个元素(设当前元素的奇偶性为 x,x ∈ {0,1}):
(注意:下面描述的“旧值”表示处理当前元素前 f 中的值。实际实现中更新顺序要保证用到的是旧值,从而不把同一轮的更新混入计算。)
-
将所有原来以 x 结尾且结尾长度为 1 的子序列(即 f[x][0] 的旧值)再追加上当前这个 x,会使它们变为“结尾长度为 2”的序列,因此把这些计数转移到 f[x][1]:
- 新的 f[x][1] 增加旧 f[x][0]。
-
可以把当前元素 x 单独作为一个新的子序列加入(这是单独选取当前元素的情况,长度为1,结尾长度为1,奇偶为 x),因此 +1 加入 f[x][0]。
-
也可以把当前元素 x 追加到那些原本以非 x(即 x^1)结尾的所有稳定子序列后面:无论那些以非 x 结尾的序列结尾长度是 1 还是 2,追加 x 都会使结尾变成“长度为 1 且奇偶为 x”(因为前后奇偶不同,新的连续段长度是 1)。因此把旧 f[x^1][0] 和旧 f[x^1][1] 的计数都加到 f[x][0] 上。
-
注意:不能把当前 x 追加到那些旧的以 x 结尾且已经有结尾长度为 2 的序列(旧 f[x][1]),因为那样会产生连续三个相同奇偶性的结尾,从而违反“稳定”的定义 —— 因此没有从旧 f[x][1] 向任何状态的转移。
-
每一步累加都要做模运算(题目要求对 10^9+7 取模),以防溢出。
结果收集:
- 在处理完数组的全部元素后,所有合法的非空稳定子序列都被计入了四个状态 f[0][0], f[0][1], f[1][0], f[1][1] 中(每个子序列按照其结尾奇偶和结尾段长度归类计数)。因此最终答案是这四项之和,对模取余后返回。
为什么这样能覆盖且仅覆盖“稳定子序列”:
- 任何稳定子序列的构造过程都可以看作在某些位置依次选择元素并且不会出现三个连续同奇偶性的追加过程。上面的转移覆盖了三种构造操作:开始一个单元素子序列、把当前元素追加到与其奇偶相同但结尾长度为1的序列(使其结尾长度变为2)、把当前元素追加到与其奇偶不同的序列(使结尾段长度变为1)。所有这些操作都被允许并计入;而任何会产生长度为3的追加操作被显式禁止(没有相应转移)。因此按前缀动态维护即可完整计数所有合法子序列。
- 从空状态逐元素推进并累加旧子序列(状态是“截至当前位置为止可以形成的子序列”),不会重复计数也不会漏计,因为每次“追加当前元素”的操作只用旧的计数并按其能否合法转移来分配到新的类别。
举例(简短示范,nums = [1, 3, 5],均为奇数,奇偶用 1 表示):
- 处理第一个 1:旧值均 0。新单元素子序列 +1 → f[1][0]=1(表示 [1])。总计 1。
- 处理第二个 3(奇偶 1):
- 把旧 f[1][0](即 [1])追加得到 f[1][1](表示 [1,3],结尾长度变为2)。
- 作为单独元素新增 [3] 计入 f[1][0];此外没有来自偶数端的追加。最终 f[1][0]=2(包含 [1] 仍保留在计数集合中,和 [3]),f[1][1]=1([1,3])。总计 3,对应子序列 [1], [3], [1,3]。
- 处理第三个 5(奇偶 1):
- 把旧 f[1][0](2 个)追加到 f[1][1],得到新的 f[1][1];
- 单独新增 [5] 加到 f[1][0];
- 没有来自偶数的追加。最终总计 6,正好是示例中稳定子序列的数量。
额外说明:
- 因为更新是就地进行的,必须注意更新顺序以保证第一步使用的是“旧的” f[x][0] 而不是已经被本次循环修改后的值;一种等价的做法是先把旧值缓存到临时变量再做计算,或按照先把 f[x][1] 用旧 f[x][0] 增加、再用旧的 f[x^1][*] 和 +1 去更新 f[x][0],两者在逻辑上等价。题中给出的实现就采用了一个顺序保证正确性的就地更新方式。
时间复杂度与额外空间复杂度:
- 时间复杂度:O(n),n 为数组长度。每个元素处理时做常数次的加法、取模等操作。
- 额外空间复杂度:O(1)。只维护常数个计数(2×2 的状态矩阵和若干临时变量),不随 n 增长而增长。
Go完整代码如下:
package main
import (
"fmt"
)
func countStableSubsequences(nums []int) int {
const mod = 1_000_000_007
f := [2][2]int{}
for _, x := range nums {
x %= 2
f[x][1] = (f[x][1] + f[x][0]) % mod
f[x][0] = (f[x][0] + f[x^1][0] + f[x^1][1] + 1) % mod
}
return (f[0][0] + f[0][1] + f[1][0] + f[1][1]) % mod
}
func main() {
nums := []int{1, 3, 5}
result := countStableSubsequences(nums)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def countStableSubsequences(nums):
MOD = 10**9 + 7
# f[even][type], type 0 表示以该数结尾的稳定子序列,type 1 表示不以该数结尾但涉及该数的稳定子序列
f = [[0, 0], [0, 0]]
for x in nums:
x %= 2
# 先更新 f[x][1]:因为要用到旧的 f[x][0],所以要先保存旧值
f[x][1] = (f[x][1] + f[x][0]) % MOD
# 更新 f[x][0]
f[x][0] = (f[x][0] + f[x^1][0] + f[x^1][1] + 1) % MOD
# 返回所有类型的子序列总数
return (f[0][0] + f[0][1] + f[1][0] + f[1][1]) % MOD
def main():
nums = [1, 3, 5]
result = countStableSubsequences(nums)
print(result)
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <vector>
using namespace std;
const int MOD = 1000000007;
int countStableSubsequences(vector<int>& nums) {
// f[even][type], type 0 表示以该数结尾的稳定子序列,type 1 表示不以该数结尾但涉及该数的稳定子序列
int f[2][2] = {{0, 0}, {0, 0}};
for (int x : nums) {
x %= 2;
// 先更新 f[x][1]:因为要用到旧的 f[x][0],所以要先保存旧值
f[x][1] = (f[x][1] + f[x][0]) % MOD;
// 更新 f[x][0]
f[x][0] = (f[x][0] + f[x^1][0] + f[x^1][1] + 1) % MOD;
}
// 返回所有类型的子序列总数
return (f[0][0] + f[0][1] + f[1][0] + f[1][1]) % MOD;
}
int main() {
vector<int> nums = {1, 3, 5};
int result = countStableSubsequences(nums);
cout << result << endl;
return 0;
}