2763. Sum of Imbalance Numbers of All Subarrays
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
(where0 <= 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:
- Consider every possible subarray of
nums
- Calculate the imbalance number for each subarray
- 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
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:
- Before insertion: We might have a gap between two consecutive elements in our sorted list.
- 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 between5
and8
- 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:
-
Initialize for each starting position: For each left endpoint
i
, create an emptySortedList
and set the imbalance countercnt = 0
. -
Extend the subarray: For each right endpoint
j
fromi
ton-1
, we addnums[j]
to our sorted structure and update the imbalance count. -
Find insertion position: Use
bisect_left(nums[j])
to find indexk
wherenums[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)
-
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
) andnums[j] - sl[h] > 1
, we're creating a new gap on the left side, socnt += 1
-
Check right gap: If element at index
k
exists (k < len(sl)
) andsl[k] - nums[j] > 1
, we're creating a new gap on the right side, socnt += 1
-
Check filled gap: If both neighbors exist (
h >= 0
andk < len(sl)
) and originallysl[k] - sl[h] > 1
, then insertingnums[j]
between them fills this existing gap, socnt -= 1
-
-
Add element and accumulate: Insert
nums[j]
into the sorted list, then add the currentcnt
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
, socnt += 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
, socnt += 1
- Filled gap check:
5 - 2 = 3 > 1
was a gap, socnt -= 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 EvaluatorExample 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
ton-1
, iteratingn
times - The inner loop runs from
j = i
ton-1
, iterating up ton
times in the worst case
Within the inner loop, the following operations are performed:
sl.bisect_left(nums[j])
: Binary search operation takingO(log n)
time in the worst case when the SortedList contains up ton
elementssl.add(nums[j])
: Insertion into a SortedList takingO(log n)
time- Array access operations
sl[h]
andsl[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 ton
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 takeO(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
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
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 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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!