这两道LeetCode连续题目(3129和3130)围绕"稳定二进制数组"这一概念展开,属于典型的动态规划结合组合数学的题型。所谓稳定二进制数组,根据题目描述需要满足以下两个约束条件:
这类问题在实际应用中对应着多种场景:
最直观的解法是生成所有可能的二进制数组然后筛选符合条件的。对于长度为n的数组,共有2^n种可能,当n=20时已经超过百万种组合,显然无法通过测试用例。
采用动态规划需要明确定义状态:
状态转移方程:
code复制dp0[i] = dp0[i-1] + dp1[i-1] # 前一位可以是0或1
dp1[i] = dp0[i-1] # 前一位只能是0
初始值:
计算示例(n=3):
在基础题上增加了第三个限制:
3. 数组中恰好有k个1
这使得问题转变为带约束的组合计数问题,需要重新设计状态转移。
定义dp[i][j][l]:
状态转移方程:
code复制dp[i][0][l] = dp[i-1][0][l] + dp[i-1][1][l] # 添加0不影响1的计数
dp[i][1][l] = dp[i-1][0][l-1] # 添加1时需要从l-1转移
需要特别注意:
由于当前状态只依赖前一个状态,可将三维数组压缩为二维:
题目要求结果对10^9+7取模,需要注意:
python复制MOD = 10**9 + 7
def numberOfStableArrays(n, k):
if k == 0:
return 1 if n >=0 else 0
if k > (n +1)//2: # 最大可能1的数量
return 0
# dp[end_with][count_1]
dp_prev = [[0]*(k+1) for _ in range(2)]
dp_prev[0][0] = 1 # 长度为1,以0结尾,0个1
dp_prev[1][1] = 1 # 长度为1,以1结尾,1个1
for i in range(2, n+1):
dp_curr = [[0]*(k+1) for _ in range(2)]
for l in range(k+1):
# 当前以0结尾
dp_curr[0][l] = (dp_prev[0][l] + dp_prev[1][l]) % MOD
# 当前以1结尾
if l > 0:
dp_curr[1][l] = dp_prev[0][l-1] % MOD
dp_prev = dp_curr
return (dp_prev[0][k] + dp_prev[1][k]) % MOD
时间复杂度:O(nk)
需要填充nk的状态表格,每个状态计算时间为O(1)
空间复杂度:O(k)
通过状态压缩仅需维护两个k大小的二维数组
有效测试场景应包括:
这类问题可以延伸至:
在解决这类问题时,关键是要准确识别状态定义和转移条件,同时注意优化空间使用。通过将大问题分解为重叠子问题,动态规划能够有效避免重复计算,显著提升效率。