T3699.锯齿形数组的总数 I
https://leetcode.cn/problems/number-of-zigzag-arrays-i/
给你 三个整数 n、l 和 r。
长度为 n 的锯齿形数组定义如下:
- 每个元素的取值范围为
[l, r]。 - 任意 两个 相邻的元素都不相等。
- 任意 三个 连续的元素不能构成一个 严格递增 或 严格递减 的序列。
返回满足条件的锯齿形数组的总数。
由于答案可能很大,请将结果对 109 + 7 取余数。
序列 被称为 严格递增 需要满足:当且仅当每个元素都严格大于它的前一个元素(如果存在)。
序列 被称为 严格递减 需要满足,当且仅当每个元素都严格小于它的前一个元素(如果存在)。
示例 1:
输入:n = 3, l = 4, r = 5
输出:2
解释:
在取值范围 [4, 5] 内,长度为 n = 3 的锯齿形数组只有 2 种:
[4, 5, 4][5, 4, 5]
示例 2:
输入:n = 3, l = 1, r = 3
输出:10
解释:
在取值范围 [1, 3] 内,长度为 n = 3 的锯齿形数组共有 10 种:
[1, 2, 1],[1, 3, 1],[1, 3, 2][2, 1, 2],[2, 1, 3],[2, 3, 1],[2, 3, 2][3, 1, 2],[3, 1, 3],[3, 2, 3]
所有数组均符合锯齿形条件。
提示:
3 <= n <= 20001 <= l < r <= 2000
这是一个可以使用动态规划(Dynamic Programming)来解决的问题。
解题思路
为了满足条件:
- 每个元素的取值范围在
。为了方便计算,我们将这些数映射到 的区间,其中 。 - 相邻元素不相等。
- 连续三个元素不能单调递增或单调递减。这意味着如果前一次状态是“上升”的(即
),下一次状态必须是“下降”的(即 );反之亦然。
我们可以定义状态来记录当前数组的长度、最后一个元素的值,以及上一步是“上升”还是“下降”。 设:
dp0[v]表示长度为且以值 结尾,且最后一步是下降(即前一个元素大于 )的合法数组数量。 dp1[v]表示长度为且以值 结尾,且最后一步是上升(即前一个元素小于 )的合法数组数量。
1. 边界情况(长度为 2 时)
对于长度为 2 的数组
- 若最后一步是下降,即
,则 可以取 中的任意值。此时共有 种选择。 - 若最后一步是上升,即
,则 可以取 中的任意值。此时共有 种选择。
2. 状态转移
当长度从
- 如果当前步是下降(到达
),那么前一步必须是上升(到达 且 ): - 如果当前步是上升(到达
),那么前一步必须是下降(到达 且 ):
如果直接计算上述转移,每次转移需要 dp0 的前缀和以及 dp1 的后缀和,将状态转移优化到
Python 代码
python
class Solution:
def zigZagArrays(self, n: int, l: int, r: int) -> int:
MOD = 1_000_000_007
M = r - l + 1
if M < 2:
return 0 # 相邻元素不能相等,若只有一个可选值则无法构造
# dp0[v]: 最后一个元素是 v,且最后一步是“下降” (即前一个元素 > v)
# dp1[v]: 最后一个元素是 v,且最后一步是“上升” (即前一个元素 < v)
dp0 = [M - 1 - v for v in range(M)]
dp1 = [v for v in range(M)]
for _ in range(3, n + 1):
# 计算 dp0 的前缀和,用于快速计算 sum(dp0[u] for u < v)
pref0 = [0] * M
s = 0
for v in range(M):
s = (s + dp0[v]) % MOD
pref0[v] = s
# 计算 dp1 的后缀和,用于快速计算 sum(dp1[u] for u > v)
suff1 = [0] * M
s = 0
for v in range(M - 1, -1, -1):
s = (s + dp1[v]) % MOD
suff1[v] = s
next_dp0 = [0] * M
next_dp1 = [0] * M
for v in range(M):
# 这一步下降到 v,前一步必须是从更高级别的 u 上升到 u
if v + 1 < M:
next_dp0[v] = suff1[v + 1]
# 这一步上升到 v,前一步必须是从更低级别的 u 下降到 u
if v - 1 >= 0:
next_dp1[v] = pref0[v - 1]
dp0 = next_dp0
dp1 = next_dp1
return (sum(dp0) + sum(dp1)) % MOD复杂度分析
- 时间复杂度:
,其中 。每次循环我们进行常数次大小为 的线性扫描。在最坏情况下( ),计算量约为 次操作,运行时间在 0.1 秒左右。 - 空间复杂度:
。我们只维护当前步和上一步的状态数组,因此空间复杂度与 无关,仅取决于可选数值的数量。