Number of subarrays having sum exactly equal to K
This article would help you understand how two problems which seem so similar can have different approaches depending on the allowed input. If you are interested in learning more about the two-pointer technique and some interesting approaches along with their subtle differences then you are at the right place. For full blown solutions to these problems you can always refer LeetCode. This article intends to just “Spot the Differences”.
Problem 1
In an array A
of 0
s and 1
s, how many non-empty subarrays have sum S
?
Approach 1: Prefix sums using dictionary
This approach is similar to the approach generally taken for 2-sum problem. Go checkout 2-sum problem if you haven’t already. Let’s break down this problem first.
The problem of finding the number of subarrays with a given sum can be further divided into two problems:
- Find all the contiguous subarrays of the given array.
- Find the sum of each of the subarrays to check if it’s equal to the desired sum.
A subarray is defined by two indices i
and j
and the elements between them form the sub-array. The total number of combinations for i
and j
would be O(N²). Can we optimize this?
Here is the part where you need to remember the algorithm you would have used for 2-sum
problem 🤓. The problem of 2-sum
says, find two numbers such that they add up to a specific target.
Look at how similar the problems are now. We now have a prefix_sum
which is derived from the given array, a running_sum
which is also derived from the given array and we are given the target value which is the desired subarray sum. Thus, at any given point when we have the currentrunning_sum
we just have to check if the dictionary has a key i.e.prefix_sum
such that
prefix_sum + desired_sum = running_sum
or to put it otherwise,running_sum — desired_sum
is present in prefix sum dictionary.
By now you would have understood that we need to maintain a running sum for the array. The running sum is nothing but an ongoing sum for the array. We store this sum in a prefix_sum
dictionary with key
as the sum and value
as the number of times we encountered that sum.
The value corresponding to the key in the prefix_sum
dictionary would basically give us the number of prefix arrays that can be removed to get the subarrays of the desired_sum
.
Approach 2: Sliding window approach using two pointers
Sliding window — In any sliding window based problem we have two pointers. The right pointer whose job is to expand the current window and then we have the left pointer whose job is to contract a given window. At any point in time only one of these pointers moves and the other one remains fixed.
In this case the right
pointer would expand the window while the sum of the window is less or equal to the desired sum. When the sum of the window exceeds the desired sum we then can contract the window by throwing out a few elements from the left. The contraction continues while the sum is greater than the desired sum.
And in this process, we keep counting the windows whose sum is equal to the desired sum.
Problem 2:
Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals to k.
Before looking at the differences let’s look at how similar these two problems are.
Both problems want us to find out the number of continuous subarrays with a given sum.
Woah ! This is some crazy amount of similarity.
Are these problems duplicates? What is the difference then 😱😰
The input allowed in both problems is the key here. While Problem 1 allows only binary numbers. Problem 2 deals with integers. Thus, Problem 2 not only deals with 0’s and 1’s but all the positive as well as all the negative numbers. Yes, the negative is highlighted for a reason. Let’s look at how we would have solved this.
Approach 1: Prefix sums using dictionary
This approach works in the same manner as for Problem 1. After all this approach is well adopted for 2-sum
. As usual, we would save all the prefix sums and our goal is to find out if removal of any of the prefixes would help us attain the desired window sum.
And that works like a charm.
Approach 2: Sliding window approach using two pointers
If you haven’t thought of this approach already then probably you should now. After all sliding window approach fits so well when we are working with subarrays. But wait, think before you start implementing it. I am guilty of actually going ahead with this approach and later on realizing that this won’t work. And that is the motivation behind this article i.e. to show how a minor detail miss can screw up an approach.
Ideally, the right
pointer would expand the window to increase the sum of the window and left
pointer would contract the window to decrease the sum. Now read that again. Are you sure that statement would hold true when your array also contains negative numbers?
When sliding a window using left
and right
pointers we always have a well defined role for both the pointers. When we have negative numbers present in the array this role starts to fade away. When we move the right
pointer the sum might increase or decrease based on the number we encounter and the same holds true for left
pointer.
Thus, whenwindow_sum > desired_sum
contracting the window by moving the left pointer forward does not guarantee that window_sum
would decrease.
When during the expansion step window_sum
became greater than desired_sum,
normally we would have stopped the window expansion at this point and started window contraction to remove some extra elements from the left. This contraction step helped us to decrease thewindow_sum
and have a window with sum which is less than the desired_sum
so that we could start the expansion step again.
But now that we have negative elements in the array even further expansion i.e. continue moving the right pointer can result in decrease in the
window_sum
.
This means if we would have stopped the expansion step when window_sum
got greater than the desired_window
we would just miss out on this new expanded window. But then that also means we are missing the contracted window we might get at this point.
This clearly indicates just the introduction of negative numbers led to fading of the roles for left
and right
pointers because of the non-deterministic behavior.
Clearly the straightforward sliding window approach fails for Problem 2.
Share your views in the comment section. If you have any suggestions for other spot-the-difference problems please share.
Hit the clap 👏 button if you liked the article. ❤