最小替换子串长度
问题描述
给定一个包含 N 个整数的数组 A,其中每个元素编号从 0 到 N-1,并且一个整数 K。数组 A 的每个元素都在 1 到 K 的范围内。
定义一个长度为 N 的数组 B 为好数组,如果它满足以下条件:
- 每个元素
B[i]都在1到K的范围内。 - 对于所有从
0到N-2的索引i,满足B[i] + B[i+1] = A[i] + A[i+1]。
你的任务是计算满足上述条件的好数组的总数。
测试样例
样例1:
输入:
N = 3, K = 2, A = [1, 2, 1]
输出:2
样例2:
输入:
N = 4, K = 3, A = [1, 3, 2, 1]
输出:1
样例3:
输入:
N = 2, K = 1, A = [1, 1]
输出:1
思路解析
我们需要找到满足特定条件的数组 B 的数量。具体条件是:
- 数组
B的长度为N,且每个元素B[i]都在1到K的范围内。 - 对于所有从
0到N-2的索引i,满足B[i] + B[i+1] = A[i] + A[i+1]。
可以使用动态规划(Dynamic Programming)来解决这个问题。定义一个二维数组 dp,其中 dp[i][j] 表示在位置 i 时,B[i] 取值为 j 的情况下,满足条件的数组 B 的数量。
解题代码
def solution(N: int, K: int, A: list[int]) -> int:
# 初始化 dp 数组
dp = [[0] * (K + 1) for _ in range(N)]
# 初始化第一个位置
for j in range(1, K + 1):
dp[0][j] = 1 # 隐藏关键步骤
# 状态转移
for i in range(1, N):
for j in range(1, K + 1):
for k in range(1, K + 1):
if j + k == A[i-1] + A[i]: # 隐藏关键步骤
dp[i][j] += dp[i-1][k] # 隐藏关键步骤
# 计算最终结果
result = sum(dp[N-1][j] for j in range(1, K + 1)) # 隐藏关键步骤
return result
代码模块详解
- 初始化:对于第一个位置
i=0,B[0]可以是1到K中的任意一个值。因此,dp[0][j] = 1对于所有1 <= j <= K。 - 状态转移:对于每个位置
i从1到N-1,我们需要计算dp[i][j]。对于每个可能的B[i]值j,我们需要找到所有可能的B[i-1]值k,使得B[i-1] + B[i] = A[i-1] + A[i]。 - 结果:最终结果是所有可能的
B[N-1]值的和,即sum(dp[N-1][j])对于所有1 <= j <= K。
复杂度分析
时间复杂度分析
当前代码使用了三重循环来实现动态规划的状态转移。具体来看:
- 外层循环:遍历数组
A的每个位置i,从1到N-1。这个循环的时间复杂度是O(N)。 - 中层循环:对于每个位置
i,遍历所有可能的B[i]值j,从1到K。这个循环的时间复杂度是O(K)。 - 内层循环:对于每个位置
i和每个可能的B[i]值j,遍历所有可能的B[i-1]值k,从1到K。这个循环的时间复杂度是O(K)。
因此,总的时间复杂度是 O(N * K * K),即 O(N * K^2)。
空间复杂度分析
当前代码使用了一个二维数组 dp 来存储动态规划的状态。具体来看:
- 二维数组
dp:dp的大小是N * (K + 1),因为我们需要存储每个位置i的所有可能的B[i]值的状态。因此,空间复杂度是O(N * K)。
总结
-
时间复杂度:
O(N * K^2) -
空间复杂度:
O(N * K)
总结和思考
-
问题分解:
- 将问题分解为更小的子问题,逐步解决。
- 确定每个子问题的状态和状态转移方程。
-
状态转移优化:
- 尝试减少状态转移的复杂度,例如通过预处理或使用更高效的数据结构。
- 考虑是否可以使用滚动数组或其他技巧来优化空间复杂度。
-
边界条件:
- 确保正确处理边界条件,例如数组的第一个和最后一个位置。
- 初始化状态时要特别注意,确保初始状态正确。