返回

Binary Subarrays With Sum(二进制子数组与和):理论与实践

前端

导论:LeetCode与930. Binary Subarrays With Sum

LeetCode 是一个备受推崇的在线编程学习平台,凭借其庞大且质量上乘的编程题目库,它帮助无数程序员磨砺算法与编程技能。其中,930. Binary Subarrays With Sum(二进制子数组与和) 是一个颇具挑战性的题目,它考验了程序员对二进制数组、子数组和异或等概念的掌握,以及对动态规划算法的理解与应用能力。

题目阐述:寻找符合条件的子数组

题目 :给定一个二进制数组nums和一个整数k,求出nums中和为k的非空子数组的个数。

示例 1

输入:nums = [1,0,1,0,1], k = 2
输出:4
解释:
- [1,0,1]的和为2。
- [0,1,1]的和为2。
- [1,1]的和为2。
- [1]的和为2。
因此,符合条件的子数组个数为4。

示例 2

输入:nums = [0,0,0,0,0], k = 0
输出:15
解释:
除了所有元素为0的子数组之外,还有以下子数组的和为0:
- []的和为0
因此,符合条件的子数组个数为15。

直观算法:穷举与优化

面对930. Binary Subarrays With Sum,我们可以采用一种直观的方法,即穷举所有可能的子数组,并计算每个子数组的和,来找到满足条件的子数组个数。然而,这种方法的时间复杂度为O(n^3) ,其中n为数组nums的长度,显然是无法接受的。

为了优化算法,我们可以利用异或 的性质:对于一个二进制数组,若子数组的异或和等于k,那么该子数组的和必定等于k。因此,我们可以通过计算前缀异或和,来快速判断子数组的和是否等于k。

构建动态规划:前缀异或和与状态转移

有了异或的性质之后,我们可以构建一个动态规划算法。首先,我们计算出数组nums的前缀异或和preXOR,其中preXOR[i]表示nums的前i个元素的异或和。然后,我们定义一个状态dp[i],表示以nums的第i个元素结尾的子数组中,满足和为k的子数组的个数。

状态转移方程

dp[i] = dp[j] + 1, 其中j < i且preXOR[i] - preXOR[j] = k

该方程的含义是,如果存在一个子数组[j, i]的异或和等于k,那么以nums的第i个元素结尾的子数组中,满足和为k的子数组的个数就等于以nums的第j个元素结尾的子数组中,满足和为k的子数组的个数加1。

代码实现:Python解决方案

def numSubarraysWithSum(nums, k):
  # 计算前缀异或和
  preXOR = [0] * len(nums)
  preXOR[0] = nums[0]
  for i in range(1, len(nums)):
    preXOR[i] = preXOR[i - 1] ^ nums[i]

  # 定义状态dp
  dp = [0] * len(nums)
  if preXOR[0] == k:
    dp[0] = 1

  # 状态转移
  for i in range(1, len(nums)):
    for j in range(i):
      if preXOR[i] - preXOR[j] == k:
        dp[i] += dp[j] + 1

  # 返回结果
  return dp[-1]

算法性能:时间与空间

利用动态规划算法,我们可以在O(n^2) 的时间复杂度内解决930. Binary Subarrays With Sum。在空间复杂度方面,我们需要额外存储前缀异或和preXOR和状态dp,因此空间复杂度也为O(n)

总结与展望

通过对930. Binary Subarrays With Sum的深入解析,我们不仅掌握了解决该问题的具体算法,更重要的是,我们对二进制数组、子数组、异或和动态规划等概念有了更深刻的理解。这些知识不仅对解决LeetCode上的其他问题大有裨益,也为我们今后的编程生涯奠定了坚实的基础。

在今后的文章中,我们将继续探索LeetCode上的经典题目,并分享更多编程技巧和算法奥秘。敬请期待!