1918. Kth Smallest Subarray Sum
Problem Description
The problem presents us with an array of integers, nums
, and asks us to find the kth
smallest sum of any subarray within this array. A subarray is defined as a contiguous part of the original array—which means any sequence of elements that are next to each other without gaps—and the sum of a subarray is the total sum of its elements. So, if nums
is [1, 2, 3]
, then [1, 2]
, [2, 3]
, and [1, 2, 3]
are all subarrays, with sums 3
, 5
, and 6
, respectively.
In simple terms, we want to look through all possible continuous sequences of numbers in the list, calculate the sum for each, and find out which is the kth
smallest among these sums.
Intuition
The challenge lies in efficiently finding the kth
smallest sum without having to compute and sort all possible subarray sums, which would be a very time-consuming process, especially for large arrays.
The intuition behind this kind of problem is to use Binary Search in an innovative way. Instead of searching for an explicit value in an array, we use Binary Search to find the smallest sum s
such that there are at least k
subarrays in nums
whose sums are less than or equal to s
. This is not a straightforward application of Binary Search because there is no sorted list of subarray sums to begin with.
Here's how the solution works:
First, we define a function f(s)
which counts how many subarrays have a sum less than or equal to s
. We use a two-pointer technique to maintain a sliding window of elements whose sum is less than or equal to s
. As we iterate through nums
, we keep expanding our window by adding new elements to the sum t
, and we shrink the window from the left by removing elements whenever the total sum exceeds s
. This way, f(s)
returns true if there are at least k
such subarrays.
Next, we set up our binary search range between l
and r
where l
is the minimum element in nums
(since no subarray sum can be smaller than the smallest element) and r
is the sum of all elements in nums
(since no subarray sum can be larger than this).
Using the bisect_left
method from the bisect
module, we perform our Binary Search within the range [l, r]
. The key
argument in bisect_left
allows us to use the function f
to check if a mid-value is feasible (i.e., the count of subarrays with sum less than or equal to this value is at least k
). The method zeroes in on the smallest s
that satisfies f(s)
, which will be the kth smallest subarray sum in nums
.
This approach is efficient because each iteration of Binary Search narrows down the range of possible sums by half, and for each such guess, counting the subarrays takes linear time, resulting in a total time complexity of O(n log x), where x
is the maximum subarray sum.
Learn more about Binary Search and Sliding Window patterns.
Solution Approach
The kthSmallestSubarraySum
function in the provided Python code leverages binary search, which is a classic optimization for problems involving sorted arrays or monotonic functions. In this case, the monotonic function is the number of subarrays with sums less than or equal to a given value.
The code defines a helper function f(s)
to use within the binary search. We can describe what each part of this function does and how it integrates with the binary search mechanism:
-
Sliding Window via Two Pointers
f(s)
uses a sliding window with two pointers,i
andj
, which represent the start and end of the current subarray being considered.- As we iterate over the array with
i
, we add eachnums[i]
to the temporary sumt
. - If the sum
t
exceeds the values
, we subtract elements starting fromnums[j]
and incrementj
to reducet
. This maintains the window where the subarray sum is less than or equal tos
.
-
Counting Subarrays
- The line
cnt += i - j + 1
is crucial. It adds the number of subarrays ending ati
for which the sum is less than or equal tos
. - This works because for every new element added to the window, there are
i - j + 1
subarrays. For instance, if our window is[nums[j], ..., nums[i]]
, then[nums[i]]
,[nums[i-1], nums[i]]
, up to[nums[j], ..., nums[i]]
are all valid subarrays.
- The line
-
- The actual search takes place over a range of potential subarray sums, from
min(nums)
tosum(nums)
. We use the binary search algorithm to efficiently find the smallest sum that has at leastk
subarrays less than or equal to it. - The
bisect_left
function from thebisect
module is used with a customkey
function, which is thef(s)
we defined earlier. Thekey
function transforms the value we're comparing against in the binary search. bisect_left
will find the smallest value within the range[l, r]
for whichf(s)
returnsTrue
, indicating it is the kth smallest sum.
- The actual search takes place over a range of potential subarray sums, from
The combination of these techniques results in an efficient solution where, rather than having to explicitly sort all the subarray sums, we can deduce the kth smallest sum by narrowing down our search space logarithmically with binary search and counting subarrays linearly with the two-pointer technique.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's apply the solution approach to a small example. Suppose nums = [1, 2, 3]
and we are looking for the 2nd
smallest subarray sum.
First, we initialize our helper function f(s)
that counts subarrays with sums less than or equal to s
. Now let's illustrate this function:
-
Initializing Variables:
- We would start with
i = 0
, meaning our subarray only includesnums[0]
for now which is[1]
. The temporary sumt
starts at0
.
- We would start with
-
Sliding Window via Two Pointers:
- Let's say we're checking for
s = 3
, we want to count how many subarrays have sum≤ 3
. - At
i = 0
, we addnums[0]
tot
so,t = 1
. Sincet ≤ 3
, we continue toi = 1
. - At
i = 1
,t
becomest + nums[1] = 3
. This sum is still≤ 3
and we can include[1, 2]
. - If we move to
i = 2
,t
would become6
which exceeds3
. Therefore, we adjustj
to remove elements from the start untilt ≤ 3
again. However, with numbers[1, 2, 3]
, no subarray ending at index2
has a sum≤ 3
other than[3]
itself.
- Let's say we're checking for
-
Counting Subarrays:
- For each
i
, the number of subarrays ending ati
with a sum≤ 3
is added up. We usecnt += i - j + 1
to do this count. - Subarrays are
[1]
,[2]
,[3]
,[1, 2]
. Thus, the count here is4
.
- For each
-
Binary Search:
- We set
l = 1
(smallest element) andr = 6
(sum of all elements). We then search for the smallest sums
wheref(s)
gives us at leastk
subarrays. In this case,k = 2
. - Using binary search, we check
s = (l + r) / 2
which is3.5
initially, then we apply the helper function to count how many subarrays with sum≤ 3.5
, which is4
. Since we are looking for the2nd
smallest sum,4
subarrays mean we can try a smallers
. - We adjust our binary search boundaries to
l = 1
andr = 3
. We check fors = 2
. The subarrays within this constraint are[1]
and[2]
, which gives us count2
. Here,f(2)
would returnTrue
since it matches ourk
value. - The binary search will now try to see if there is a sum smaller than
2
that also satisfies the condition, but since2
is the sum of our smallest individual element and there's no smaller sum that could give us2
subarrays,2
is indeed our2nd
smallest sum.
- We set
By using this approach of binary search combined with a two-pointer technique to count subarrays, we efficiently find the 2nd
smallest subarray sum without having to explicitly calculate every possible subarray sum.
Solution Implementation
1from bisect import bisect_left
2from typing import List
3
4class Solution:
5 def kthSmallestSubarraySum(self, nums: List[int], k: int) -> int:
6
7 # Helper function to check if the count of subarrays with sum less than or equal to 'limit'
8 # is at least k
9 def has_k_or_more_subarrays_with_sum_at_most(limit):
10 total_sum = 0 # Sum of the current subarray
11 start = 0 # Start index for the current subarray
12 count = 0 # Count of subarrays with sum less than or equal to 'limit'
13 # Iterate over the numbers in the array
14 for end, num in enumerate(nums):
15 total_sum += num
16 # Shrink the window from the left if the total sum exceeds the limit
17 while total_sum > limit:
18 total_sum -= nums[start]
19 start += 1
20 # The count is increased by the number of subarrays ending with nums[end]
21 count += end - start + 1
22 # Check if we have at least k subarrays
23 return count >= k
24
25 # Binary search to find the kth smallest subarray sum
26 left, right = min(nums), sum(nums)
27 # Perform binary search with a custom key function by using the bisect_left function
28 kth_smallest_sum = left + bisect_left(range(left, right + 1), True,
29 key=has_k_or_more_subarrays_with_sum_at_most)
30 return kth_smallest_sum
31
32# Explanation:
33# 1. A binary search is applied to find the kth smallest sum within the range of the minimum
34# element (left) and the sum of all elements (right), inclusively.
35# 2. The `has_k_or_more_subarrays_with_sum_at_most` function checks if the number of all
36# possible subarrays with a sum less than or equal to the passed limit is at least k.
37# 3. The bisect_left function is used to find the insertion point (the kth smallest sum) for
38# which the condition in `has_k_or_more_subarrays_with_sum_at_most` returns True.
39
1class Solution {
2 public int kthSmallestSubarraySum(int[] nums, int k) {
3 // Initialize the left and right boundaries for the binary search.
4 // Assume the smallest subarray sum is large, and find the smallest element of the array.
5 int left = Integer.MAX_VALUE, right = 0;
6 for (int num : nums) {
7 left = Math.min(left, num);
8 right += num;
9 }
10 // Perform binary search to find the kth smallest subarray sum.
11 while (left < right) {
12 int mid = left + (right - left) / 2;
13 // If there are more than k subarrays with a sum <= mid, move the right pointer.
14 if (countSubarraysWithSumAtMost(nums, mid) >= k) {
15 right = mid;
16 } else {
17 // Otherwise, move the left pointer.
18 left = mid + 1;
19 }
20 }
21 // The left pointer points to the kth smallest subarray sum.
22 return left;
23 }
24
25 // Helper method to count the number of subarrays with a sum at most 's'.
26 private int countSubarraysWithSumAtMost(int[] nums, int s) {
27 int currentSum = 0, start = 0;
28 int count = 0;
29 for (int end = 0; end < nums.length; ++end) {
30 currentSum += nums[end];
31 // If the current sum exceeds 's', shrink the window from the left.
32 while (currentSum > s) {
33 currentSum -= nums[start++];
34 }
35 // Add the number of subarrays ending at index 'end' with a sum at most 's'.
36 count += end - start + 1;
37 }
38 return count;
39 }
40}
41
1class Solution {
2public:
3 // Function to find the kth smallest subarray sum
4 int kthSmallestSubarraySum(vector<int>& nums, int k) {
5 // Initialize the left and right boundaries for binary search
6 int leftBound = INT_MAX, rightBound = 0;
7 // Find the smallest number in the nums array and the total sum
8 for (int x : nums) {
9 leftBound = min(leftBound, x);
10 rightBound += x;
11 }
12
13 // Lambda function to count the subarrays with sum less than or equal to 'sum'
14 auto countSubarraysLEQ = [&](int sum) {
15 int totalCount = 0;
16 int subarraySum = 0;
17 for (int i = 0, j = 0; i < nums.size(); ++i) {
18 subarraySum += nums[i];
19 // Increment j to maintain the condition that subarraySum <= sum
20 while (subarraySum > sum) {
21 subarraySum -= nums[j++];
22 }
23 // Update the total count of subarrays ending at index i
24 totalCount += i - j + 1;
25 }
26 return totalCount;
27 };
28
29 // Binary search to find the kth smallest subarray sum
30 while (leftBound < rightBound) {
31 int mid = leftBound + (rightBound - leftBound) / 2;
32 if (countSubarraysLEQ(mid) >= k) {
33 rightBound = mid;
34 } else {
35 leftBound = mid + 1;
36 }
37 }
38
39 // The left bound will be the kth smallest subarray sum
40 return leftBound;
41 }
42};
43
1// Function to find the kth smallest subarray sum
2function kthSmallestSubarraySum(nums: number[], k: number): number {
3 // Initialize the left and right boundaries for binary search
4 let leftBound = Number.MAX_SAFE_INTEGER;
5 let rightBound = 0;
6
7 // Find the smallest number in the nums array and the total sum
8 nums.forEach((x) => {
9 leftBound = Math.min(leftBound, x);
10 rightBound += x;
11 });
12
13 // Function to count the subarrays with sum less than or equal to 'sum'
14 const countSubarraysLEQ = (sum: number): number => {
15 let totalCount = 0;
16 let subarraySum = 0;
17 for (let i = 0, j = 0; i < nums.length; ++i) {
18 subarraySum += nums[i];
19 // Increment j to maintain the condition that subarraySum <= sum
20 while (subarraySum > sum) {
21 subarraySum -= nums[j++];
22 }
23 // Update the total count of subarrays ending at index i
24 totalCount += i - j + 1;
25 }
26 return totalCount;
27 };
28
29 // Binary search to find the kth smallest subarray sum
30 while (leftBound < rightBound) {
31 let mid = leftBound + Math.floor((rightBound - leftBound) / 2);
32 if (countSubarraysLEQ(mid) >= k) {
33 rightBound = mid;
34 } else {
35 leftBound = mid + 1;
36 }
37 }
38
39 // The left bound will be the kth smallest subarray sum
40 return leftBound;
41}
42
Time and Space Complexity
Time Complexity
The given Python code implements a binary search combined with a two-pointer technique to find the k-th smallest subarray sum in an integer list nums
.
-
The binary search is performed on a range from the minimum element in
nums
to the sum of all elements innums
. This range isr - l + 1
, wherel
is the minimum value (i.e.,min(nums)
) andr
is the sum of the numbers innums
. Since we are effectively halving the search space with each iteration, the time complexity for the binary search alone isO(log(r - l))
. -
Inside the binary search, we use function
f()
to count the number of subarray sums that are less than or equal to a given sums
by using a sliding window technique with the two pointersi
andj
. This is done by iterating through the array once for each value ofs
. The inner loop movesj
appropriately but does not iterate more thann
times across all iterations ofi
since it only subtracts the values that were previously added. Therefore, for eachs
, the functionf()
has a time complexity ofO(n)
wheren
is the length ofnums
.
Combining the binary search and the sliding window, the total time complexity is O(n * log(r - l))
.
Space Complexity
The space complexity of the code is O(1)
, not counting the input nums
. This is because the code only uses a fixed number of integer variables (l
, r
, s
, t
, j
, and cnt
) and does not create additional data structures that grow with the size of the input.
Learn more about how to find time and space complexity quickly using problem constraints.
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
https algomonster s3 us east 2 amazonaws com cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Want a Structured Path to Master System Design Too? Don’t Miss This!