- Is the input array sorted?
Input: nums = [1, 1, 1], k = 2
Output: 2
Input: nums = [1, 2, 3], k = 3
Output: 2
- All elements are
0
:
Input: nums = [0, 0, 0], k = 0
Output: 6
- Positive + Negative + Zero:
Input: nums = [0, 1, -1, 0], k = 0
Output: 5
Input: nums = [1, -1, 0, 1, -1], k = 0
Output: 7
Input: nums = [1, -1, 1, -1, 1], k = 0
- All elements are equals to
k
:
Input: nums = [2, 2, 2], k = 2
Output: 3
fun subarraySum(nums: IntArray, k: Int): Int {
var count = 0
for (start in 0 until nums.size) {
for (end in start until nums.size) {
var sum = 0
for (k in star..end) {
sum += nums[k]
}
if (sum == k) count++
}
}
return count
}
- Time Complexity:
O(n^3)
. - Space Complexity:
O(1)
.
We can sum up while iterating the element, because sum of start..end
is the sum of start..end - 1
+ nums[end]
.
fun subarraySum(nums: IntArray, k: Int): Int {
var count = 0
for (start in 0 until nums.size) {
var sum = 0
for (end in start until nums.size) {
sum += nums[end]
if (sum == k) count++
}
}
return count
}
- Time Complexity:
O(n^2)
. - Space Complexity:
O(1)
.
We have prefix sum array pre[i]
, which is the sum of 0..i
. And suppose we have another index j
which i < j
, then:
pre[j] = nums[0] + nums[1] + ... + ... + ( ... + nums[j])
= nums[0] + nums[1] + ... + nums[i] + (nums[i + 1] + ... + nums[j])
= pre[i] + (nums[i + 1] + ... + nums[j])
// We transform to
pre[i] + (nums[i + 1] + ... + nums[j]) = pre[j]
// Transform to
pre[i] + k = pre[j]
// Transform to
k = pre[j] - pre[i]
// Transform to (the key formula)
pre[i] = pre[j] - k
To search pre[i] + k == pre[j]
, we can actually search if pre[j] - k
exists, that is pre[i]
exists. Let's see an example:
k = 3
i = 0 1 2 3 4
nums = [2, 1, 3, -1, 1]
pre = [2, 3, 6, 5, 6]
i j
|--i
|------------j
|--k---|
pre[i] = 2 + 1
pre[j] = 2 + 1 + 3 + -1 + 1
= pre[i] + 3 + -1 + 1
pre[j] - pre[i] = 3 + -1 + 1
= k
We can apply the same approach in problem 1. Two Sum, we maintain a hash table with prefix sum as key and the count of the prefix sum as value. Then we can iterate j
to sum up the prefixSum
(that is pre[j]
), then check if pre[j] - k
(pre[i]
) exists in the hash table. If it exists, then we found a subarray A[i+1 : j]
with sum k
, and we add the count of the subarray from hash table to the result.
pre[j] - k = pre[i]
indicates that sum ofA[i+1 : j] == k
Let's see an example:
// Example 1
[1, 2, 3, 4], k = 5
i
prefixSum = 1
countMap.containsKey(1 - 5) = countMap.containsKey(-4) = false
countMap[1] = 1 {0:1, 1:1} // Add the current prefixSum to the map
[1, 2, 3, 4], k = 5
i
prefixSum = 3
countMap.containsKey(3 - 5) = countMap.containsKey(-2) = false
countMap[3] = 1 {0:1, 1:1, 3:1}
[1, 2, 3, 4], k = 5
^^^^
i
prefixSum = 6
countMap.containsKey(6 - 5) = countMap.containsKey(1) = true
count += countMap[1] = 1
countMap[6] = 1 {0:1, 1:1, 3:1, 6:1}
// Example 2
[1, 2, 3, 4], k = 7
^^^^
i
prefixSum = 10
countMap.containsKey(10 - 5) = countMap.containsKey(3) = true
count += countMap[3] = 1
countMap[10] = 1 {0:1, 1:1, 3:1, 6:1}
fun subarraySum(nums: IntArray, k: Int): Int {
var count = 0
var prefixSum = 0
val countMap = HashMap<Int, Int>()
// We don't set value here
// countMap[0] = 1
for (i in 0 until nums.size) {
prefixSum += nums[i]
// We found the current `prefixSum == k`
if (prefixSum == k) count++
// We update the answer when we find the subarray
// with sum k by checking `prefixSum - k`
if (countMap.containsKey(prefixSum - k)) {
count += countMap[prefixSum - k]!!
}
// Increment the occurrence of current `prefixSum`
countMap[prefixSum] = (countMap[prefixSum] ?: 0) + 1
}
return count
}
// Or equivalent
fun subarraySum(nums: IntArray, k: Int): Int {
var count = 0
var prefixSum = 0
val countMap = HashMap<Int, Int>()
// The CATCH: This is for the case for `prefixSum` == k or `prefixSum - k == 0`
countMap[0] = 1
for (i in 0 until nums.size) {
prefixSum += nums[i]
if (countMap.containsKey(prefixSum - k)) {
count += countMap[prefixSum - k]!!
}
countMap[prefixSum] = (countMap[prefixSum] ?: 0) + 1
}
return count
}
There are some critical points in our implementation and approach:
- We don't maintain the
prefixSum
array because we can iterate once to calculate theprefixSum
and put it in the hash table. - For the case
prefixSum == k
, we have to add the count ofprefixSum == k
to the result, so we initialize theprefixSum
map with0
as key and1
as value.
k = 2
nums = [1, 1, 2]
prefix = 1 2 4
* --> prefixSum - k = 0
- If
prefixSum - k
exists, we don't increment the count by 1 because it leads to WA, there might be multipleprefixSum - k
exists, so we sum up the count by the number ofprefixSum - k
exists. (Input[0, 0, 0]
, k =0
)
input = 0, 0, 0
prefix= 0, 0, 0
- This problem can't be solved by sliding window, because there is no good way to decide when to shrink the window when the number is negative (
nums = [-1, -1, 1]
andk = 0
) or contains0
(nums = [0, 1, 1, 0]
andk = 2
).
// Sliding Window, it doesn't work if nums contains negative number or 0, it only works if all numbers are positive.
fun subarraySum(nums: IntArray, k: Int): Int {
if (nums.size == 1 && nums[0] != k) return 0
var start = 0
var end = 0
var sum = 0
var count = 0
while (end < nums.size) {
sum += nums[end]
while (sum > k) {
sum -= nums[start]
start++
}
if (sum == k) {
count++
}
end++
}
return count
}
Based on the characteristics of sliding window:
- If wider window is valid, then narrower window is also valid.
k = 3
nums = [1, 1, 1]
L R // valid
L R // invalid
- If narrow window is invalid, then wider window is invalid as well.
k = 2
nums = [1, 1, 1, -1]
L R // invalid
L R // valid
L R // invalid
- Time Complexity:
O(n)
. - Space Complexity:
O(n)
.
Initial
count = 0
countMap[0] = 1
// ----------------
// Example 1
[5, 8], k = 5
i
prefixSum = 5
countMap.containsKey(5 - 5) = countMap.containsKey(0) = true
count += countMap[0] = 1
countMap[5] = 1 {0:1, 5:1}
[5, 8], k = 5
i
prefixSum = 13
countMap.containsKey(13 - 5) = countMap.containsKey(8) = false+
countMap[13] = 1 {0:1, 5:1, 13:1}
// ----------------
// Example 2
[5, 8], k = 8
i
prefixSum = 5
countMap.containsKey(5 - 8) = countMap.containsKey(-3) = false
countMap[5] = 1 {0:1, 5:1}
[5, 8], k = 8
i
prefixSum = 13
countMap.containsKey(13 - 8) = countMap.containsKey(5) = true
countMap[13] = 1 {0:1, 5:1, 13:1}
// ----------------
// Example 3
[5, 8], k = 13
i
prefixSum = 13
countMap.containsKey(13 - 8) = countMap.containsKey(5) = true
countMap[13] = 1 {0:1, 5:1, 13:1}