3728. Stable Subarrays With Equal Boundary and Interior Sum
Problem Description
You are given an integer array capacity.
A subarray capacity[l..r] is called stable if it satisfies both of the following conditions:
-
Length requirement: The subarray contains at least 3 elements (i.e.,
r - l + 1 >= 3). -
Sum requirement: The first element and the last element of the subarray are both equal to the sum of all elements strictly between them. In other words:
capacity[l] = capacity[r] = capacity[l + 1] + capacity[l + 2] + ... + capacity[r - 1]
Your task is to count how many such stable subarrays exist in the given array, and return that count.
Key points to understand:
- A subarray is a contiguous, non-empty sequence of elements within the array.
- The "middle" elements are those at indices
l + 1throughr - 1. Since the subarray must have at least 3 elements, there is always at least one middle element. - Both endpoints
capacity[l]andcapacity[r]must be equal to each other and equal to the sum of the middle elements.
Example walkthrough:
Suppose capacity = [3, 1, 2, 3]:
- Consider the subarray
capacity[0..3] = [3, 1, 2, 3].- First element:
3, last element:3. - Sum of middle elements:
1 + 2 = 3. - Since
3 = 3 = 3, this is a stable subarray.
- First element:
- Subarrays of length less than 3 are never counted, and any subarray failing the sum condition is excluded.
The answer for this example would be 1, since only one subarray satisfies all conditions.
How We Pick the Algorithm
Why Prefix Sums?
This problem maps to Prefix Sums through a short path in the full flowchart.
The condition capacity[l] = capacity[r] = sum of middle elements translates to a prefix-sum equation solvable with hash table lookups.
Open in FlowchartIntuition
The brute-force way to solve this problem is to check every pair (l, r) and verify the condition, but that requires computing the sum of middle elements for each pair, leading to O(n^2) or worse. We need a smarter way.
Step 1: Express the middle sum with prefix sums.
The sum of the middle elements capacity[l + 1] + ... + capacity[r - 1] can be written using a prefix sum array s, where s[i] is the sum of the first i elements:
middle sum = s[r] - s[l + 1]
So the stable condition becomes:
capacity[l] = capacity[r] = s[r] - s[l + 1]
Step 2: Separate the variables.
The equation above mixes l and r together, which makes counting hard. The trick is to rearrange it so that everything about l is on one side and everything about r is on the other:
-
From
capacity[l] = s[r] - s[l + 1], we get:capacity[l] + s[l + 1] = s[r] -
The other requirement is simply:
capacity[l] = capacity[r]
Now notice something powerful: the left endpoint l is fully described by the pair (capacity[l], capacity[l] + s[l + 1]), and the right endpoint r is fully described by the pair (capacity[r], s[r]). A subarray [l..r] is stable exactly when these two pairs match.
Step 3: Count matches with a hash table.
Once the condition is reduced to "two pairs being equal," the problem becomes a classic counting pattern:
- Sweep the right endpoint
rfrom left to right. - For each
r, all valid left endpoints are thoselwithl <= r - 2(to guarantee at least one middle element) whose pair(capacity[l], capacity[l] + s[l + 1])equals(capacity[r], s[r]). - Maintain a hash table
cntthat counts how many left endpoints with each pair we have seen so far. Before processingr, we insert the newly eligible left endpointl = r - 2into the table, then look upcnt[(capacity[r], s[r])]and add it to the answer.
This turns an expensive nested search into a single pass with O(1) lookups per right endpoint, giving an overall O(n) solution. The key insight is algebraically decoupling l and r so that matching subarrays can be counted by matching hash keys.
Pattern Learn more about Prefix Sum patterns.
Solution Approach
The solution combines prefix sums, a hash table, and enumeration of the right endpoint. Let's walk through the implementation step by step.
Step 1: Build the prefix sum array.
s = list(accumulate(capacity, initial=0))
Here s[i] is the sum of the first i elements of capacity, with s[0] = 0. This lets us compute the sum of any middle segment in constant time:
capacity[l + 1] + ... + capacity[r - 1] = s[r] - s[l + 1]
Step 2: Reformulate the stable condition as a key match.
From the intuition, the subarray [l..r] is stable when:
capacity[l] = capacity[r]capacity[l] + s[l + 1] = s[r]
So we describe each left endpoint l by the key (capacity[l], capacity[l] + s[l + 1]), and each right endpoint r by the key (capacity[r], s[r]). A stable subarray corresponds exactly to a matching pair of keys.
Step 3: Sweep the right endpoint while maintaining a hash table.
cnt = defaultdict(int)
for r in range(2, n):
l = r - 2
cnt[(capacity[l], capacity[l] + s[l + 1])] += 1
ans += cnt[(capacity[r], s[r])]
- The loop starts at
r = 2, since a stable subarray needs at least 3 elements, so the smallest valid right endpoint is index2. - Before counting for the current
r, we register the newly eligible left endpointl = r - 2into the hash tablecnt. This ordering guarantees that the table contains exactly the left endpoints0, 1, ..., r - 2— precisely those that leave at least one middle element betweenlandr. - Then we query
cnt[(capacity[r], s[r])]. Every left endpoint stored under this key satisfies both stability equations with the currentr, so we add that count directly to the answer.
Step 4: Return the accumulated answer.
return ans
Why this works correctly:
The incremental insertion of l = r - 2 is the subtle part. It enforces the length constraint implicitly — at the moment we process right endpoint r, only left endpoints at distance >= 2 exist in the table, so every counted pair automatically forms a subarray of length at least 3. No explicit length check is ever needed.
Complexity Analysis:
- Time complexity:
O(n)— building the prefix sum takes one pass, and the main loop performsO(1)hash table operations (one insertion, one lookup) per right endpoint. - Space complexity:
O(n)— for the prefix sum arraysand the hash tablecnt, which holds at mostn - 2keys.
Example Walkthrough
Let's trace the algorithm with capacity = [1, 1, 1, 1, 2].
Step 1 — Build the prefix sum array.
index: 0 1 2 3 4 capacity: 1 1 1 1 2 s = [0, 1, 2, 3, 4, 6] # s[i] = sum of first i elements
Step 2 — Sweep the right endpoint r from 2 to 4, maintaining the hash table cnt.
Recall the two keys:
- Left endpoint
lis registered under key(capacity[l], capacity[l] + s[l + 1]) - Right endpoint
rqueries key(capacity[r], s[r])
Iteration r = 2:
- Insert the newly eligible left endpoint
l = 0:- Key:
(capacity[0], capacity[0] + s[1]) = (1, 1 + 1) = (1, 2) cnt = {(1, 2): 1}
- Key:
- Query key for
r = 2:(capacity[2], s[2]) = (1, 2)→ found1match. ans = 1✅ This corresponds to subarray[1, 1, 1]at indices0..2: endpoints1 = 1, middle sum1. Stable.
Iteration r = 3:
- Insert
l = 1:- Key:
(capacity[1], capacity[1] + s[2]) = (1, 1 + 2) = (1, 3) cnt = {(1, 2): 1, (1, 3): 1}
- Key:
- Query key for
r = 3:(capacity[3], s[3]) = (1, 3)→ found1match. ans = 2✅ This corresponds to subarray[1, 1, 1]at indices1..3. Stable.
Note that the full subarray [1, 1, 1, 1] (indices 0..3) is not counted here: its left endpoint 0 is stored under key (1, 2), which does not match the query (1, 3) — exactly reflecting that its middle sum 1 + 1 = 2 differs from the endpoint value 1.
Iteration r = 4:
- Insert
l = 2:- Key:
(capacity[2], capacity[2] + s[3]) = (1, 1 + 3) = (1, 4) cnt = {(1, 2): 1, (1, 3): 1, (1, 4): 1}
- Key:
- Query key for
r = 4:(capacity[4], s[4]) = (2, 4)→ found0matches. ansstays2. ❌ No subarray ending at index4is stable — every stored left endpoint has value1, which can never equalcapacity[4] = 2.
Step 3 — Return the result.
The final answer is 2, matching the two stable subarrays capacity[0..2] and capacity[1..3].
Observations from the trace:
- The length requirement was never checked explicitly: by inserting
l = r - 2just before querying, the table only ever contains left endpoints at distance>= 2fromr. - Each iteration performed exactly one insertion and one lookup, confirming the
O(n)running time.
Solution Implementation
1from typing import List
2from itertools import accumulate
3from collections import defaultdict
4
5
6class Solution:
7 def countStableSubarrays(self, capacity: List[int]) -> int:
8 # prefix_sum[i] stores the sum of capacity[0..i-1],
9 # so the sum of capacity[l..r] equals prefix_sum[r + 1] - prefix_sum[l]
10 prefix_sum = list(accumulate(capacity, initial=0))
11 n = len(capacity)
12 result = 0
13
14 # counter maps a key (capacity[left], capacity[left] + prefix_sum[left + 1])
15 # to the number of left endpoints producing that key.
16 # A subarray [left, right] is "stable" when:
17 # 1. capacity[left] == capacity[right]
18 # 2. sum(capacity[left + 1 .. right - 1]) == capacity[left]
19 # Condition 2 can be rewritten using prefix sums as:
20 # prefix_sum[right] - prefix_sum[left + 1] == capacity[left]
21 # => capacity[left] + prefix_sum[left + 1] == prefix_sum[right]
22 counter = defaultdict(int)
23
24 # The smallest valid subarray has length 3, so right starts at index 2
25 for right in range(2, n):
26 # Each new right endpoint enables one new left endpoint (right - 2),
27 # since left must satisfy left <= right - 2
28 left = right - 2
29 counter[(capacity[left], capacity[left] + prefix_sum[left + 1])] += 1
30
31 # Count all previously registered left endpoints that pair with
32 # this right endpoint to form a stable subarray
33 result += counter[(capacity[right], prefix_sum[right])]
34
35 return result
361class Solution {
2 /**
3 * Counts the number of "stable" subarrays.
4 * A subarray [left, right] (length >= 3) is stable when:
5 * 1. capacity[left] == capacity[right]
6 * 2. The sum of the elements strictly between them
7 * (capacity[left+1] + ... + capacity[right-1]) equals capacity[left].
8 *
9 * Approach:
10 * - Build a prefix sum array so any range sum is O(1).
11 * - Sweep the right endpoint from left to right. For each candidate left
12 * endpoint l (which must satisfy l <= r - 2), store a key describing
13 * what a matching right endpoint must look like:
14 * (capacity[l], capacity[l] + prefixSum[l + 1])
15 * A right endpoint r matches when:
16 * capacity[r] == capacity[l] (same boundary value)
17 * prefixSum[r] == capacity[l] + prefixSum[l + 1] (middle sum == boundary value)
18 * - A hash map counts how many left endpoints produce each key, so each
19 * right endpoint contributes the count of its matching key in O(1).
20 *
21 * Time complexity: O(n)
22 * Space complexity: O(n)
23 */
24 public long countStableSubarrays(int[] capacity) {
25 int n = capacity.length;
26
27 // prefixSum[i] = sum of capacity[0 .. i-1]
28 long[] prefixSum = new long[n + 1];
29 for (int i = 1; i <= n; ++i) {
30 prefixSum[i] = prefixSum[i - 1] + capacity[i - 1];
31 }
32
33 // Maps the expected (boundaryValue, requiredPrefixSum) of a right endpoint
34 // to the number of left endpoints that demand it.
35 Map<Key, Integer> leftEndpointCount = new HashMap<>();
36
37 long answer = 0;
38
39 // The right endpoint must be at least index 2 so the subarray has length >= 3.
40 for (int right = 2; right < n; ++right) {
41 // The newly available left endpoint for this right endpoint.
42 int left = right - 2;
43
44 // Register the key a matching right endpoint must satisfy:
45 // capacity[right] == capacity[left]
46 // prefixSum[right] == capacity[left] + prefixSum[left + 1]
47 Key expectedKey = new Key(capacity[left], capacity[left] + prefixSum[left + 1]);
48 leftEndpointCount.merge(expectedKey, 1, Integer::sum);
49
50 // Count all left endpoints that pair with the current right endpoint.
51 Key currentKey = new Key(capacity[right], prefixSum[right]);
52 answer += leftEndpointCount.getOrDefault(currentKey, 0);
53 }
54
55 return answer;
56 }
57
58 /**
59 * Immutable composite key: the boundary value and the prefix sum
60 * a matching right endpoint must have.
61 */
62 private record Key(int boundaryValue, long requiredPrefixSum) {
63 }
64}
651// Custom hash functor so that std::pair<int, long long> can be used
2// as a key in an unordered_map.
3struct PairHash {
4 size_t operator()(const pair<int, long long>& p) const {
5 // Combine the hashes of both components; the shift reduces
6 // collisions between symmetric pairs.
7 return hash<int>()(p.first) ^ (hash<long long>()(p.second) << 1);
8 }
9};
10
11class Solution {
12public:
13 long long countStableSubarrays(vector<int>& capacity) {
14 int n = capacity.size();
15
16 // prefixSum[i] holds the sum of capacity[0 .. i-1].
17 // prefixSum[0] = 0 acts as the base case.
18 vector<long long> prefixSum(n + 1, 0);
19 for (int i = 1; i <= n; ++i) {
20 prefixSum[i] = prefixSum[i - 1] + capacity[i - 1];
21 }
22
23 // A subarray [l .. r] (length >= 3) is "stable" when:
24 // 1. capacity[l] == capacity[r]
25 // 2. The sum of the middle part capacity[l+1 .. r-1]
26 // equals the boundary value capacity[l].
27 //
28 // The middle sum is prefixSum[r] - prefixSum[l + 1], so the
29 // condition rewrites to:
30 // capacity[l] + prefixSum[l + 1] == prefixSum[r]
31 //
32 // For each left endpoint l we store the key
33 // (capacity[l], capacity[l] + prefixSum[l + 1])
34 // and for each right endpoint r we look up the key
35 // (capacity[r], prefixSum[r]).
36 unordered_map<pair<int, long long>, int, PairHash> keyCount;
37
38 long long answer = 0;
39
40 // The smallest valid right endpoint is index 2,
41 // since a stable subarray needs at least 3 elements.
42 for (int right = 2; right < n; ++right) {
43 // Register the newly available left endpoint (right - 2),
44 // which keeps a gap of at least one middle element.
45 int left = right - 2;
46 pair<int, long long> leftKey = {capacity[left],
47 capacity[left] + prefixSum[left + 1]};
48 keyCount[leftKey] += 1;
49
50 // Count all previously registered left endpoints that
51 // form a stable subarray with the current right endpoint.
52 pair<int, long long> rightKey = {capacity[right], prefixSum[right]};
53 auto it = keyCount.find(rightKey);
54 if (it != keyCount.end()) {
55 answer += it->second;
56 }
57 }
58
59 return answer;
60 }
61};
621/**
2 * Counts the number of "stable" subarrays in the given array.
3 *
4 * A subarray [l, r] (with at least 3 elements) is considered stable when:
5 * 1. capacity[l] === capacity[r] (first and last elements are equal)
6 * 2. The sum of the elements strictly between l and r equals capacity[l].
7 *
8 * Approach:
9 * - Build a prefix sum array so that prefixSum[i] = capacity[0] + ... + capacity[i - 1].
10 * - The middle sum of subarray [l, r] is prefixSum[r] - prefixSum[l + 1].
11 * The stability condition becomes:
12 * capacity[l] === capacity[r]
13 * capacity[l] + prefixSum[l + 1] === prefixSum[r]
14 * - Sweep the right endpoint r from left to right. For each r, every index
15 * l <= r - 2 has already been registered in a hash map keyed by the pair
16 * (capacity[l], capacity[l] + prefixSum[l + 1]). Looking up the key
17 * (capacity[r], prefixSum[r]) gives the number of valid left endpoints.
18 *
19 * Time complexity: O(n) — single pass with O(1) map operations.
20 * Space complexity: O(n) — prefix sums and the hash map.
21 *
22 * @param capacity - The input array of numbers.
23 * @returns The total count of stable subarrays.
24 */
25function countStableSubarrays(capacity: number[]): number {
26 const n: number = capacity.length;
27
28 // prefixSum[i] holds the sum of the first i elements of capacity.
29 const prefixSum: number[] = Array(n + 1).fill(0);
30 for (let i = 1; i <= n; i++) {
31 prefixSum[i] = prefixSum[i - 1] + capacity[i - 1];
32 }
33
34 // Maps the composite key "value,value + prefixSum" of a candidate left
35 // endpoint to how many times that key has occurred so far.
36 const leftKeyCount: Map<string, number> = new Map<string, number>();
37
38 // Running total of stable subarrays found.
39 let answer: number = 0;
40
41 // Iterate over every possible right endpoint (subarray needs >= 3 elements).
42 for (let right = 2; right < n; right++) {
43 // The newest left endpoint that keeps a gap of at least one element.
44 const left: number = right - 2;
45
46 // Register the left endpoint's key:
47 // (capacity[left], capacity[left] + prefixSum[left + 1])
48 const leftKey: string = `${capacity[left]},${capacity[left] + prefixSum[left + 1]}`;
49 leftKeyCount.set(leftKey, (leftKeyCount.get(leftKey) ?? 0) + 1);
50
51 // Query with the right endpoint's key:
52 // (capacity[right], prefixSum[right])
53 // Every matching left endpoint forms one stable subarray.
54 const rightKey: string = `${capacity[right]},${prefixSum[right]}`;
55 answer += leftKeyCount.get(rightKey) ?? 0;
56 }
57
58 return answer;
59}
60Time and Space Complexity
-
Time Complexity:
O(n), wherenis the length of the arraycapacity.- Building the prefix sum array
sviaaccumulatetakesO(n)time. - The main loop runs from
r = 2ton - 1, performing a constant number of operations per iteration: one dictionary insertion (cnt[(capacity[l], capacity[l] + s[l + 1])] += 1) and one dictionary lookup (cnt[(capacity[r], s[r])]), each of which isO(1)on average. - Therefore, the total time complexity is
O(n) + O(n) = O(n).
- Building the prefix sum array
-
Space Complexity:
O(n), wherenis the length of the arraycapacity.- The prefix sum array
sstoresn + 1elements, requiringO(n)space. - The hash map
cntstores at mostO(n)key-value pairs, since at most one new key is inserted per loop iteration. - Hence, the overall space complexity is
O(n).
- The prefix sum array
Pattern Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Registering the left endpoint at the wrong moment (breaking the length constraint)
The most subtle part of this solution is when a left endpoint becomes eligible. A frequent mistake is to insert the left endpoint l = r - 1 (or even l = r) into the hash table before querying, or to pre-populate the table with all left endpoints before the sweep begins:
# WRONG: pre-populating every left endpoint up front
for l in range(n):
counter[(capacity[l], capacity[l] + prefix_sum[l + 1])] += 1
for r in range(2, n):
result += counter[(capacity[r], prefix_sum[r])]
Why it's wrong: A stable subarray requires at least one middle element, i.e., l <= r - 2. If the table contains left endpoints at distance 0 or 1 from r, the query can match pairs like [x, x] (length 2) where capacity[l] == capacity[r] and the prefix-sum equation coincidentally holds (for example, when capacity[l] = 0). These invalid subarrays inflate the answer.
Solution: Insert exactly one new left endpoint, l = r - 2, at the start of each iteration — before the query for the current r. This guarantees the table holds precisely the endpoints 0, 1, ..., r - 2, so the length constraint is enforced implicitly:
for right in range(2, n):
left = right - 2
counter[(capacity[left], capacity[left] + prefix_sum[left + 1])] += 1
result += counter[(capacity[right], prefix_sum[right])]
Pitfall 2: Using a single value as the hash key instead of a pair
It is tempting to key the hash table only on capacity[l] (to match equal endpoints) and then verify the sum separately, or conversely to key only on capacity[l] + prefix_sum[l + 1]:
# WRONG: key captures only one of the two conditions counter[capacity[left]] += 1 ... result += counter[capacity[right]] # ignores the sum requirement entirely
Why it's wrong: Stability demands both capacity[l] == capacity[r] and capacity[l] + prefix_sum[l + 1] == prefix_sum[r] simultaneously. A single-value key matches pairs that satisfy only one condition, and there is no way to filter the table's aggregated counts afterward without degrading to O(n²).
Solution: Encode both conditions into a composite tuple key, so a key match is equivalent to stability:
- Left endpoint key:
(capacity[l], capacity[l] + prefix_sum[l + 1]) - Right endpoint key:
(capacity[r], prefix_sum[r])
Pitfall 3: Off-by-one errors in the prefix sum indices
With prefix_sum[i] defined as the sum of the first i elements (prefix_sum[0] = 0), the middle-segment sum is:
capacity[l + 1] + ... + capacity[r - 1] = prefix_sum[r] - prefix_sum[l + 1]
A common slip is writing prefix_sum[r + 1] - prefix_sum[l] (which sums capacity[l..r], including both endpoints) or prefix_sum[r - 1] - prefix_sum[l + 1] (which drops capacity[r - 1] from the middle).
Solution: Verify the indices with a tiny concrete case before coding the sweep. For capacity = [3, 1, 2, 3], prefix_sum = [0, 3, 4, 6, 9]; the middle sum of [0..3] should be 1 + 2 = 3, and indeed prefix_sum[3] - prefix_sum[1] = 6 - 3 = 3. Anchoring the formula against an example catches off-by-one bugs immediately.
Pitfall 4: Falling back to brute-force enumeration
A straightforward triple-condition check over all (l, r) pairs:
# WRONG approach for large inputs: O(n^2) pairs, each verified in O(1) with prefix sums
for l in range(n):
for r in range(l + 2, n):
if capacity[l] == capacity[r] == prefix_sum[r] - prefix_sum[l + 1]:
result += 1
Why it's a problem: Even with prefix sums making each check O(1), enumerating all pairs is O(n²), which times out for large arrays.
Solution: Reformulate the pair condition as a key-matching problem (as in the main approach), reducing the work per right endpoint to O(1) hash operations and the total to O(n).
Ready to land your dream job?
Unlock your dream job with a 5-minute quiz for a personalized study roadmap!
Get My RoadmapWhat is the best way of checking if an element exists in an unsorted array once in terms of time complexity? Select the best that applies.
Recommended Readings
Prefix Sum Technique Explained Prefix Sum A prefix sum transforms an array into a new array of running totals For an input array arr define prefix k as the sum of all elements before index k prefix 0 0 prefix 1 arr 0 prefix 2 arr 0 arr 1 and so on The power
Coding Interview 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
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!