Facebook Pixel

2763. Sum of Imbalance Numbers of All Subarrays

HardArrayHash TableOrdered Set
Leetcode Link

Problem Description

You are given a 0-indexed integer array nums. Your task is to find the sum of imbalance numbers across all possible subarrays.

The imbalance number of an array is defined as follows:

  • First, sort the array to get sarr
  • Count how many indices i (where 0 <= i < n - 1) satisfy the condition: sarr[i+1] - sarr[i] > 1
  • This count is the imbalance number

In other words, the imbalance number counts how many "gaps" exist in the sorted array where consecutive elements differ by more than 1.

For example:

  • Array [2, 5, 3] becomes [2, 3, 5] when sorted
  • Check consecutive pairs: 3 - 2 = 1 (no gap), 5 - 3 = 2 (gap exists)
  • Imbalance number = 1

A subarray is any contiguous sequence of elements from the original array. You need to:

  1. Consider every possible subarray of nums
  2. Calculate the imbalance number for each subarray
  3. Return the sum of all these imbalance numbers

The solution uses an ordered list to maintain elements as we extend the subarray. When adding a new element nums[j]:

  • It finds where nums[j] would be inserted in the sorted order
  • It checks if adding this element creates new gaps with its neighbors
  • It also checks if adding this element fills an existing gap (reducing the imbalance count)
  • The running count cnt tracks the current subarray's imbalance number
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we don't need to sort each subarray from scratch to calculate its imbalance number. Instead, we can maintain a dynamically sorted structure as we expand our subarray one element at a time.

Think about what happens when we add a new element to an already sorted subarray. The new element will be inserted at some position, and this insertion can only affect the gaps around its insertion point. Specifically:

  1. Before insertion: We might have a gap between two consecutive elements in our sorted list.
  2. After insertion: The new element might:
    • Create new gaps with its immediate neighbors
    • Fill an existing gap between two elements

Let's trace through this idea. Suppose we have a sorted list [2, 5, 8] with imbalance count of 2 (gaps at 2→5 and 5→8). Now we want to add element 6:

  • Element 6 will be inserted between 5 and 8
  • Check left neighbor: 6 - 5 = 1 (no new gap)
  • Check right neighbor: 8 - 6 = 2 (creates a gap)
  • But wait! Originally 8 - 5 = 3 was a gap, and now it's split into two parts
  • Net effect: We removed one gap (5→8) and added one gap (6→8), so the count remains 2

This observation leads to our approach: For each starting position i, we incrementally build subarrays by adding elements one by one. We use a sorted list to maintain order and a counter cnt to track the current imbalance number. When adding nums[j]:

  • Find where it fits in the sorted order (using binary search)
  • Check if it creates gaps with its would-be neighbors
  • Check if it fills an existing gap
  • Update the running count accordingly

This way, we efficiently calculate the imbalance number for all subarrays in O(n² log n) time, avoiding the need to sort each subarray independently.

Solution Approach

We enumerate all possible subarrays by fixing the left endpoint i and extending the right endpoint j from i to n-1. For each subarray [i, j], we maintain its elements in a sorted order using a SortedList data structure.

Implementation Steps:

  1. Initialize for each starting position: For each left endpoint i, create an empty SortedList and set the imbalance counter cnt = 0.

  2. Extend the subarray: For each right endpoint j from i to n-1, we add nums[j] to our sorted structure and update the imbalance count.

  3. Find insertion position: Use bisect_left(nums[j]) to find index k where nums[j] should be inserted. This gives us:

    • k: The index of the first element >= nums[j] (or length of list if none exists)
    • h = k - 1: The index of the last element < nums[j] (or -1 if none exists)
  4. Update imbalance count: Before inserting nums[j], we check how it affects the gaps:

    • Check left gap: If element at index h exists (h >= 0) and nums[j] - sl[h] > 1, we're creating a new gap on the left side, so cnt += 1

    • Check right gap: If element at index k exists (k < len(sl)) and sl[k] - nums[j] > 1, we're creating a new gap on the right side, so cnt += 1

    • Check filled gap: If both neighbors exist (h >= 0 and k < len(sl)) and originally sl[k] - sl[h] > 1, then inserting nums[j] between them fills this existing gap, so cnt -= 1

  5. Add element and accumulate: Insert nums[j] into the sorted list, then add the current cnt to our answer.

Example Walkthrough:

Consider nums = [2, 5, 3] and subarray starting at index 0:

  • Start with empty SortedList, cnt = 0
  • Add 2: List becomes [2], no gaps, cnt = 0
  • Add 5:
    • k = 1, h = 0
    • Check: 5 - 2 = 3 > 1, so cnt += 1
    • List becomes [2, 5], cnt = 1
  • Add 3:
    • k = 1 (position where 3 should go), h = 0
    • Left check: 3 - 2 = 1, no gap
    • Right check: 5 - 3 = 2 > 1, so cnt += 1
    • Filled gap check: 5 - 2 = 3 > 1 was a gap, so cnt -= 1
    • List becomes [2, 3, 5], cnt = 1

The time complexity is O(n² log n) where n is the length of the array, as we have two nested loops and each insertion/search in SortedList takes O(log n) time.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through nums = [4, 1, 3, 2] and calculate the sum of imbalance numbers for all subarrays.

Starting from index 0 (element 4):

  • Subarray [4]: SortedList = [4], no gaps possible with one element, cnt = 0
  • Subarray [4, 1]:
    • Find position for 1: k = 0 (1 goes before 4), h = -1
    • Right gap check: 4 - 1 = 3 > 1, creates gap, cnt = 0 + 1 = 1
    • SortedList = [1, 4], imbalance = 1
  • Subarray [4, 1, 3]:
    • Find position for 3: k = 1 (3 goes between 1 and 4), h = 0
    • Left gap check: 3 - 1 = 2 > 1, creates gap, cnt = 1 + 1 = 2
    • Right gap check: 4 - 3 = 1, no gap
    • Filled gap check: Originally 4 - 1 = 3 > 1 was a gap, now filled, cnt = 2 - 1 = 1
    • SortedList = [1, 3, 4], imbalance = 1
  • Subarray [4, 1, 3, 2]:
    • Find position for 2: k = 1 (2 goes between 1 and 3), h = 0
    • Left gap check: 2 - 1 = 1, no gap
    • Right gap check: 3 - 2 = 1, no gap
    • Filled gap check: Originally 3 - 1 = 2 > 1 was a gap, now filled, cnt = 1 - 1 = 0
    • SortedList = [1, 2, 3, 4], imbalance = 0

Starting from index 1 (element 1):

  • Subarray [1]: SortedList = [1], cnt = 0
  • Subarray [1, 3]:
    • Find position for 3: k = 1, h = 0
    • Left gap check: 3 - 1 = 2 > 1, creates gap, cnt = 0 + 1 = 1
    • SortedList = [1, 3], imbalance = 1
  • Subarray [1, 3, 2]:
    • Find position for 2: k = 1, h = 0
    • Left gap check: 2 - 1 = 1, no gap
    • Right gap check: 3 - 2 = 1, no gap
    • Filled gap check: 3 - 1 = 2 > 1 was a gap, now filled, cnt = 1 - 1 = 0
    • SortedList = [1, 2, 3], imbalance = 0

Starting from index 2 (element 3):

  • Subarray [3]: SortedList = [3], cnt = 0
  • Subarray [3, 2]:
    • Find position for 2: k = 0, h = -1
    • Right gap check: 3 - 2 = 1, no gap
    • SortedList = [2, 3], imbalance = 0

Starting from index 3 (element 2):

  • Subarray [2]: SortedList = [2], cnt = 0

Total sum = 0 + 1 + 1 + 0 + 0 + 1 + 0 + 0 + 0 + 0 = 3

The algorithm efficiently tracks how each new element affects the gaps in the sorted structure, avoiding the need to re-sort and recalculate from scratch for each subarray.

Solution Implementation

1from typing import List
2from sortedcontainers import SortedList
3
4class Solution:
5    def sumImbalanceNumbers(self, nums: List[int]) -> int:
6        """
7        Calculate the sum of imbalance numbers for all subarrays.
8        An imbalance in a sorted array is when two adjacent elements differ by more than 1.
9      
10        Args:
11            nums: List of integers
12          
13        Returns:
14            Sum of imbalance counts across all subarrays
15        """
16        n = len(nums)
17        total_imbalance = 0
18      
19        # Iterate through all possible starting positions of subarrays
20        for start_idx in range(n):
21            # SortedList maintains elements in sorted order with O(log n) insertion
22            sorted_subarray = SortedList()
23            current_imbalance_count = 0
24          
25            # Extend the subarray from start_idx to the right
26            for end_idx in range(start_idx, n):
27                current_num = nums[end_idx]
28              
29                # Find where current_num would be inserted in the sorted list
30                insert_position = sorted_subarray.bisect_left(current_num)
31                left_neighbor_idx = insert_position - 1
32              
33                # Check if adding current_num creates new imbalances
34              
35                # Check imbalance with left neighbor
36                if left_neighbor_idx >= 0 and current_num - sorted_subarray[left_neighbor_idx] > 1:
37                    current_imbalance_count += 1
38              
39                # Check imbalance with right neighbor
40                if insert_position < len(sorted_subarray) and sorted_subarray[insert_position] - current_num > 1:
41                    current_imbalance_count += 1
42              
43                # Check if adding current_num removes an existing imbalance
44                # (by filling a gap between two elements that had an imbalance)
45                if (left_neighbor_idx >= 0 and 
46                    insert_position < len(sorted_subarray) and 
47                    sorted_subarray[insert_position] - sorted_subarray[left_neighbor_idx] > 1):
48                    current_imbalance_count -= 1
49              
50                # Add the current number to the sorted list
51                sorted_subarray.add(current_num)
52              
53                # Add current subarray's imbalance count to the total
54                total_imbalance += current_imbalance_count
55      
56        return total_imbalance
57
1class Solution {
2    public int sumImbalanceNumbers(int[] nums) {
3        int arrayLength = nums.length;
4        int totalImbalanceSum = 0;
5      
6        // Iterate through all possible starting positions of subarrays
7        for (int startIndex = 0; startIndex < arrayLength; ++startIndex) {
8            // TreeMap to maintain sorted order of elements in current subarray
9            TreeMap<Integer, Integer> sortedElements = new TreeMap<>();
10            int currentImbalanceCount = 0;
11          
12            // Extend the subarray from startIndex to endIndex
13            for (int endIndex = startIndex; endIndex < arrayLength; ++endIndex) {
14                int currentElement = nums[endIndex];
15              
16                // Find the smallest element greater than or equal to currentElement
17                Integer nextLarger = sortedElements.ceilingKey(currentElement);
18                // Check if adding currentElement creates an imbalance with the next larger element
19                if (nextLarger != null && nextLarger - currentElement > 1) {
20                    ++currentImbalanceCount;
21                }
22              
23                // Find the largest element less than or equal to currentElement
24                Integer previousSmaller = sortedElements.floorKey(currentElement);
25                // Check if adding currentElement creates an imbalance with the previous smaller element
26                if (previousSmaller != null && currentElement - previousSmaller > 1) {
27                    ++currentImbalanceCount;
28                }
29              
30                // If currentElement is inserted between two elements that had an imbalance,
31                // that imbalance is now resolved, so we need to decrement the count
32                if (previousSmaller != null && nextLarger != null && nextLarger - previousSmaller > 1) {
33                    --currentImbalanceCount;
34                }
35              
36                // Add the current element to the sorted map (merge handles duplicates)
37                sortedElements.merge(currentElement, 1, Integer::sum);
38              
39                // Add the current subarray's imbalance count to the total
40                totalImbalanceSum += currentImbalanceCount;
41            }
42        }
43      
44        return totalImbalanceSum;
45    }
46}
47
1class Solution {
2public:
3    int sumImbalanceNumbers(vector<int>& nums) {
4        int n = nums.size();
5        int totalImbalance = 0;
6      
7        // Iterate through all possible starting positions of subarrays
8        for (int start = 0; start < n; ++start) {
9            multiset<int> sortedSet;  // Maintains elements in sorted order
10            int currentImbalance = 0;  // Tracks imbalance count for current subarray
11          
12            // Extend the subarray from start to end
13            for (int end = start; end < n; ++end) {
14                // Find the position where nums[end] should be inserted
15                auto insertPos = sortedSet.lower_bound(nums[end]);
16              
17                // Check if inserting nums[end] creates imbalance with next element
18                // If next element exists and gap > 1, we have a new imbalance
19                if (insertPos != sortedSet.end() && *insertPos - nums[end] > 1) {
20                    ++currentImbalance;
21                }
22              
23                // Check if inserting nums[end] creates imbalance with previous element
24                // If previous element exists and gap > 1, we have a new imbalance
25                if (insertPos != sortedSet.begin() && nums[end] - *prev(insertPos) > 1) {
26                    ++currentImbalance;
27                }
28              
29                // Check if nums[end] fills a gap between two elements
30                // If it does, the previous imbalance between those elements is resolved
31                if (insertPos != sortedSet.end() && insertPos != sortedSet.begin() && 
32                    *insertPos - *prev(insertPos) > 1) {
33                    --currentImbalance;
34                }
35              
36                // Insert the current element into the sorted set
37                sortedSet.insert(nums[end]);
38              
39                // Add current subarray's imbalance count to total
40                totalImbalance += currentImbalance;
41            }
42        }
43      
44        return totalImbalance;
45    }
46};
47
1function sumImbalanceNumbers(nums: number[]): number {
2    const n: number = nums.length;
3    let totalImbalance: number = 0;
4  
5    // Iterate through all possible starting positions of subarrays
6    for (let start = 0; start < n; start++) {
7        // Use array to maintain sorted elements (will sort manually)
8        const sortedArray: number[] = [];
9        let currentImbalance: number = 0;  // Tracks imbalance count for current subarray
10      
11        // Extend the subarray from start to end
12        for (let end = start; end < n; end++) {
13            const valueToInsert: number = nums[end];
14          
15            // Find the position where nums[end] should be inserted using binary search
16            let insertPos: number = findInsertPosition(sortedArray, valueToInsert);
17          
18            // Check if inserting nums[end] creates imbalance with next element
19            // If next element exists and gap > 1, we have a new imbalance
20            if (insertPos < sortedArray.length && sortedArray[insertPos] - valueToInsert > 1) {
21                currentImbalance++;
22            }
23          
24            // Check if inserting nums[end] creates imbalance with previous element
25            // If previous element exists and gap > 1, we have a new imbalance
26            if (insertPos > 0 && valueToInsert - sortedArray[insertPos - 1] > 1) {
27                currentImbalance++;
28            }
29          
30            // Check if nums[end] fills a gap between two elements
31            // If it does, the previous imbalance between those elements is resolved
32            if (insertPos > 0 && insertPos < sortedArray.length && 
33                sortedArray[insertPos] - sortedArray[insertPos - 1] > 1) {
34                currentImbalance--;
35            }
36          
37            // Insert the current element into the sorted array at the correct position
38            sortedArray.splice(insertPos, 0, valueToInsert);
39          
40            // Add current subarray's imbalance count to total
41            totalImbalance += currentImbalance;
42        }
43    }
44  
45    return totalImbalance;
46}
47
48/**
49 * Binary search to find the insert position for a value in a sorted array
50 * Returns the index where the value should be inserted to maintain sorted order
51 */
52function findInsertPosition(sortedArray: number[], target: number): number {
53    let left: number = 0;
54    let right: number = sortedArray.length;
55  
56    // Binary search for the correct insertion position
57    while (left < right) {
58        const mid: number = Math.floor((left + right) / 2);
59        if (sortedArray[mid] < target) {
60            left = mid + 1;
61        } else {
62            right = mid;
63        }
64    }
65  
66    return left;
67}
68

Time and Space Complexity

Time Complexity: O(n² × log n)

The code uses two nested loops:

  • The outer loop runs from i = 0 to n-1, iterating n times
  • The inner loop runs from j = i to n-1, iterating up to n times in the worst case

Within the inner loop, the following operations are performed:

  • sl.bisect_left(nums[j]): Binary search operation taking O(log n) time in the worst case when the SortedList contains up to n elements
  • sl.add(nums[j]): Insertion into a SortedList taking O(log n) time
  • Array access operations sl[h] and sl[k]: O(1) time each
  • Arithmetic operations and comparisons: O(1) time

The total number of iterations across both loops is n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n²)

For each iteration, we perform O(log n) operations (bisect_left and add), resulting in a total time complexity of O(n² × log n).

Space Complexity: O(n)

The space usage comes from:

  • The SortedList sl which can store up to n elements in the worst case (when processing the entire array from index 0)
  • A few integer variables (i, j, k, h, cnt, ans, n) which take O(1) space

Therefore, the overall space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Duplicate Elements Handling

The current solution doesn't account for duplicate elements in the array. When duplicates exist, the SortedList will maintain them, but the imbalance calculation logic assumes all elements are unique.

Problem Example: For array [2, 3, 3, 5], when sorted, we have consecutive identical elements. The gap between 3 and 3 is 0, which shouldn't count as an imbalance. However, the current logic might miscount imbalances when inserting a duplicate.

Solution: Check if the element already exists before updating the imbalance count:

def sumImbalanceNumbers(self, nums: List[int]) -> int:
    n = len(nums)
    total_imbalance = 0
  
    for start_idx in range(n):
        sorted_subarray = SortedList()
        current_imbalance_count = 0
      
        for end_idx in range(start_idx, n):
            current_num = nums[end_idx]
          
            # Check if this is a duplicate
            if current_num in sorted_subarray:
                # Duplicates don't create new imbalances
                sorted_subarray.add(current_num)
                total_imbalance += current_imbalance_count
                continue
          
            insert_position = sorted_subarray.bisect_left(current_num)
            left_neighbor_idx = insert_position - 1
          
            # Rest of the logic remains the same...

2. Edge Case with Single Element Subarrays

When dealing with subarrays of size 1, there are no pairs to check for imbalances, so the imbalance count should always be 0. The current solution handles this correctly, but it's easy to overlook when modifying the code.

Verification: Ensure that when sorted_subarray has only one element, current_imbalance_count remains 0, which it does since no conditions for incrementing will be met.

3. Integer Overflow for Large Inputs

For very large arrays with many subarrays having high imbalance counts, the sum might exceed typical integer limits in some languages (though Python handles arbitrary precision integers).

Solution for other languages: Use appropriate data types (e.g., long long in C++) or modulo arithmetic if required by the problem constraints.

4. Misunderstanding Gap Definition

A common mistake is confusing the gap condition. The imbalance occurs when sarr[i+1] - sarr[i] > 1, not >= 1. This means consecutive numbers like [2, 3] do NOT create an imbalance.

Debugging tip: Add assertions or debug prints to verify the imbalance count for known test cases:

# Debug helper
def verify_imbalance(arr):
    sorted_arr = sorted(arr)
    count = 0
    for i in range(len(sorted_arr) - 1):
        if sorted_arr[i+1] - sorted_arr[i] > 1:
            count += 1
    return count
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

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

Load More