返回

由简到繁,逐层攻破和为k的子数组问题

后端





**问题** 

给你一个整数数组 nums 和一个整数 k ,请你返回子数组内元素的总和等于 k 的子数组的个数。

**示例** 

输入:nums = [1, 1, 1], k = 2
输出:2


输入:nums = [1, 2, 3], k = 3
输出:2


**解决方法** 

1. **动态规划** 

动态规划是一种解决这类问题的经典方法。我们定义状态 dp[i][j] 为前 i 个元素和为 j 的子数组的个数。那么,对于第 i 个元素,我们可以通过以下方式更新状态 dp[i][j]

dp[i][j] = dp[i-1][j] + dp[i-1][j - nums[i]]


其中,dp[i-1][j] 表示前 i-1 个元素和为 j 的子数组的个数,dp[i-1][j - nums[i]] 表示前 i-1 个元素和为 j - nums[i] 的子数组的个数。

使用这种方法,我们可以得到一个大小为 (n+1) x (k+1) 的二维数组 dp,其中 n 是数组 nums 的长度,k 是整数 k。然后,我们就可以通过查表的方式得到答案。

2. **前缀和** 

前缀和是一种预处理技术,它可以帮助我们快速计算子数组的和。我们定义前缀和数组 prefix[i] 为前 i 个元素的和。那么,对于子数组 [l, r],其和可以表示为 prefix[r] - prefix[l-1]

for(int i=0;i<=n;i++)
{
prefix[i]=prefix[i-1]+nums[i-1];
}


例如,对于数组 nums = [1, 1, 1],前缀和数组 prefix = [0, 1, 2, 3]。那么,子数组 [0, 2] 的和为 prefix[2] - prefix[0] = 2 - 0 = 2。

利用前缀和,我们可以将求解和为 k 的子数组的个数问题转化为求解前缀和等于 k 的子数组的个数问题。我们可以使用哈希表来存储前缀和及其出现的次数。然后,对于每个前缀和,我们可以通过查找哈希表来得到前缀和等于 k - prefix[i] 的子数组的个数。将这些子数组的个数相加,就可以得到答案。

3. **滑动窗口** 

滑动窗口是一种高效的算法,它可以帮助我们在线性时间内求解和为 k 的子数组的个数问题。我们定义滑动窗口的左边界为 l,右边界为 r。最初,l 和 r 都指向数组 nums 的第一个元素。然后,我们不断地移动右边界,直到窗口内的元素的和大于 k。当窗口内的元素的和大于 k 时,我们将左边界移动一位,直到窗口内的元素的和再次小于等于 k。我们将每次移动右边界时遇到的所有子数组的个数都累加到答案中。

int left = 0, right = 0;
int sum = 0;
int count = 0;

while(right < nums.length)
{
sum += nums[right];
while(sum > k)
{
sum -= nums[left];
left++;
}
count += right - left + 1;
right++;
}


**复杂度分析** 

* 动态规划:时间复杂度为 O(n*k),空间复杂度为 O(n*k)。
* 前缀和:时间复杂度为 O(n),空间复杂度为 O(k)。
* 滑动窗口:时间复杂度为 O(n),空间复杂度为 O(1)。

**结论** 

在本文中,我们探讨了 LeetCode 上的剑指 Offer II 010. 和为 k 的子数组问题。我们从问题开始,然后逐步深入探讨了问题的解决方法,包括动态规划、前缀和和滑动窗口等。无论您是编程新手还是经验丰富的程序员,相信本文都会对您有所启发。