Facebook Pixel

3728. Stable Subarrays With Equal Boundary and Interior Sum

MediumArrayHash TablePrefix Sum
LeetCode ↗

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:

  1. Length requirement: The subarray contains at least 3 elements (i.e., r - l + 1 >= 3).

  2. 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 + 1 through r - 1. Since the subarray must have at least 3 elements, there is always at least one middle element.
  • Both endpoints capacity[l] and capacity[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.
  • 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Prefix Sums?

This problem maps to Prefix Sums through a short path in the full flowchart.

Tree orgraph?noPrefixsums /range sum?yesPrefix Sums

The condition capacity[l] = capacity[r] = sum of middle elements translates to a prefix-sum equation solvable with hash table lookups.

Open in Flowchart

Intuition

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 r from left to right.
  • For each r, all valid left endpoints are those l with l <= 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 cnt that counts how many left endpoints with each pair we have seen so far. Before processing r, we insert the newly eligible left endpoint l = r - 2 into the table, then look up cnt[(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 index 2.
  • Before counting for the current r, we register the newly eligible left endpoint l = r - 2 into the hash table cnt. This ordering guarantees that the table contains exactly the left endpoints 0, 1, ..., r - 2 — precisely those that leave at least one middle element between l and r.
  • Then we query cnt[(capacity[r], s[r])]. Every left endpoint stored under this key satisfies both stability equations with the current r, 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 performs O(1) hash table operations (one insertion, one lookup) per right endpoint.
  • Space complexity: O(n) — for the prefix sum array s and the hash table cnt, which holds at most n - 2 keys.

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 l is registered under key (capacity[l], capacity[l] + s[l + 1])
  • Right endpoint r queries 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}
  • Query key for r = 2: (capacity[2], s[2]) = (1, 2) → found 1 match.
  • ans = 1 ✅ This corresponds to subarray [1, 1, 1] at indices 0..2: endpoints 1 = 1, middle sum 1. 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}
  • Query key for r = 3: (capacity[3], s[3]) = (1, 3) → found 1 match.
  • ans = 2 ✅ This corresponds to subarray [1, 1, 1] at indices 1..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}
  • Query key for r = 4: (capacity[4], s[4]) = (2, 4) → found 0 matches.
  • ans stays 2. ❌ No subarray ending at index 4 is stable — every stored left endpoint has value 1, which can never equal capacity[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 - 2 just before querying, the table only ever contains left endpoints at distance >= 2 from r.
  • 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
36
1class 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}
65
1// 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};
62
1/**
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}
60

Time and Space Complexity

  • Time Complexity: O(n), where n is the length of the array capacity.

    • Building the prefix sum array s via accumulate takes O(n) time.
    • The main loop runs from r = 2 to n - 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 is O(1) on average.
    • Therefore, the total time complexity is O(n) + O(n) = O(n).
  • Space Complexity: O(n), where n is the length of the array capacity.

    • The prefix sum array s stores n + 1 elements, requiring O(n) space.
    • The hash map cnt stores at most O(n) key-value pairs, since at most one new key is inserted per loop iteration.
    • Hence, the overall space complexity is O(n).

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 Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

What 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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More