3634. Minimum Removals to Balance Array
Problem Description
You have an integer array nums
and an integer k
.
An array is balanced when its maximum element is at most k
times its minimum element. In other words, for an array to be balanced: max(array) ≤ k × min(array)
.
Your task is to remove some elements from nums
to make it balanced. You can remove any number of elements, but the array cannot become empty.
Return the minimum number of elements you need to remove to make the remaining array balanced.
Key Points:
- A single-element array is always balanced (since max = min, the condition holds for any
k
) - You must keep at least one element in the array
- You want to minimize the number of removals
Example reasoning:
If you have nums = [1, 5, 10]
and k = 2
:
- The current array is not balanced because
max(10) > k × min(1)
→10 > 2 × 1
- If you remove
10
, the array becomes[1, 5]
which is balanced:5 ≤ 2 × 1
is false - If you remove
1
, the array becomes[5, 10]
which is balanced:10 ≤ 2 × 5
is true - So the minimum removals needed is 1
Intuition
The key insight is that in a balanced array, once we fix the minimum value, the maximum value is constrained by the relationship max ≤ k × min
. This means we can determine which elements are valid to keep once we know the minimum.
Let's think about this step by step:
-
Why sorting helps: If we sort the array, we can easily identify potential minimum values and their corresponding valid maximum values. For any element we choose as the minimum, all valid elements that can coexist with it must be within the range
[min, k × min]
. -
The greedy observation: Instead of thinking about which elements to remove, we can flip the problem - what's the maximum number of elements we can keep? The answer is the length of the array minus this maximum.
-
Trying each element as minimum: For each element
nums[i]
in the sorted array, if we treat it as the minimum value of our balanced subarray, then:- All elements to its left are smaller (can't be included as they would become the new minimum)
- We can include elements to its right only if they satisfy
element ≤ k × nums[i]
-
Finding the valid range efficiently: Since the array is sorted, for a minimum value
nums[i]
, we can use binary search to find the rightmost positionj
wherenums[j] ≤ k × nums[i]
. All elements from indexi
toj-1
can form a balanced array withnums[i]
as the minimum. -
Maximizing the kept elements: By trying each element as the potential minimum and finding its corresponding maximum valid range, we can determine the longest possible balanced subarray. The elements in the range
[i, j)
represent a valid balanced array of lengthj - i
.
The beauty of this approach is that it transforms a removal problem into a selection problem - finding the longest contiguous subarray in the sorted array that satisfies the balance condition.
Learn more about Sorting and Sliding Window patterns.
Solution Approach
The solution implements the intuition using sorting and binary search:
-
Sort the array: First, we sort
nums
in ascending order. This allows us to systematically try each element as the minimum value and efficiently find valid maximum values. -
Initialize counter: We use
cnt
to track the maximum number of elements we can keep in a balanced array. -
Enumerate each element as minimum: We iterate through each element
nums[i]
and treat it as the minimum value of a potential balanced array:for i, x in enumerate(nums):
-
Find the valid range using binary search: For each minimum value
x
, we need to find how many elements can coexist with it. The maximum allowed value isk * x
. We usebisect_right
to find the indexj
of the first element that is strictly greater thank * x
:j = bisect_right(nums, k * x)
bisect_right(nums, k * x)
returns the insertion point fork * x
in the sorted array- All elements from index
i
toj-1
(inclusive) satisfy the conditionelement ≤ k * x
- These elements form a valid balanced array with
nums[i]
as the minimum
-
Track the maximum keepable elements: The number of elements we can keep when
nums[i]
is the minimum isj - i
. We update our maximum:cnt = max(cnt, j - i)
-
Calculate minimum removals: Since
cnt
represents the maximum number of elements we can keep, the minimum number of elements to remove is:return len(nums) - cnt
Time Complexity: O(n log n)
where n
is the length of the array
- Sorting takes
O(n log n)
- For each of the
n
elements, we perform a binary search which takesO(log n)
- Total:
O(n log n) + O(n * log n) = O(n log n)
Space Complexity: O(1)
if we don't count the space used for sorting (typically O(log n)
for the sorting algorithm's recursion stack)
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 the solution with nums = [3, 1, 7, 4]
and k = 3
.
Step 1: Sort the array
- Original:
[3, 1, 7, 4]
- Sorted:
[1, 3, 4, 7]
Step 2: Try each element as the minimum
We'll iterate through each element and check how many elements we can keep if it's the minimum.
Iteration 1: i = 0
, minimum = 1
- Maximum allowed value:
k × min = 3 × 1 = 3
- Use binary search to find elements ≤ 3 in
[1, 3, 4, 7]
bisect_right([1, 3, 4, 7], 3) = 2
(position after the last 3)- Valid range: indices
[0, 2)
→ elements[1, 3]
- Elements we can keep:
2 - 0 = 2
Iteration 2: i = 1
, minimum = 3
- Maximum allowed value:
k × min = 3 × 3 = 9
- Use binary search to find elements ≤ 9 in
[1, 3, 4, 7]
bisect_right([1, 3, 4, 7], 9) = 4
(all elements are ≤ 9)- Valid range: indices
[1, 4)
→ elements[3, 4, 7]
- Elements we can keep:
4 - 1 = 3
Iteration 3: i = 2
, minimum = 4
- Maximum allowed value:
k × min = 3 × 4 = 12
- Use binary search to find elements ≤ 12 in
[1, 3, 4, 7]
bisect_right([1, 3, 4, 7], 12) = 4
- Valid range: indices
[2, 4)
→ elements[4, 7]
- Elements we can keep:
4 - 2 = 2
Iteration 4: i = 3
, minimum = 7
- Maximum allowed value:
k × min = 3 × 7 = 21
- Use binary search to find elements ≤ 21 in
[1, 3, 4, 7]
bisect_right([1, 3, 4, 7], 21) = 4
- Valid range: indices
[3, 4)
→ elements[7]
- Elements we can keep:
4 - 3 = 1
Step 3: Find the maximum keepable elements
- Maximum from all iterations:
max(2, 3, 2, 1) = 3
Step 4: Calculate minimum removals
- Total elements: 4
- Maximum keepable: 3
- Minimum removals:
4 - 3 = 1
Verification: If we remove element 1
, we get [3, 4, 7]
. Check if balanced:
max = 7
,min = 3
7 ≤ 3 × 3 = 9
✓ (balanced!)
The answer is 1 removal needed.
Solution Implementation
1from typing import List
2from bisect import bisect_right
3
4
5class Solution:
6 def minRemoval(self, nums: List[int], k: int) -> int:
7 # Sort the array to enable binary search
8 nums.sort()
9
10 # Track the maximum number of elements that can be kept
11 max_elements_to_keep = 0
12
13 # For each element as the minimum value in a valid subarray
14 for i, min_value in enumerate(nums):
15 # Find the rightmost position where nums[j] <= k * min_value
16 # This gives us the range [i, j) where all elements satisfy
17 # min_value <= nums[x] <= k * min_value
18 right_bound = bisect_right(nums, k * min_value)
19
20 # Update the maximum number of elements we can keep
21 # right_bound - i gives the count of valid elements starting from index i
22 max_elements_to_keep = max(max_elements_to_keep, right_bound - i)
23
24 # Return minimum removals = total elements - maximum elements we can keep
25 return len(nums) - max_elements_to_keep
26
1class Solution {
2 public int minRemoval(int[] nums, int k) {
3 // Sort the array to enable binary search and range finding
4 Arrays.sort(nums);
5
6 // Track the maximum size of valid subarray where max/min <= k
7 int maxValidSubarraySize = 0;
8 int arrayLength = nums.length;
9
10 // For each element as potential minimum value in the subarray
11 for (int i = 0; i < arrayLength; ++i) {
12 int rightBoundaryIndex = arrayLength;
13
14 // Check if current element * k can reach the maximum element
15 // This prevents overflow by using long multiplication
16 if (1L * nums[i] * k <= nums[arrayLength - 1]) {
17 // Find the first element greater than nums[i] * k using binary search
18 int targetValue = nums[i] * k + 1;
19 rightBoundaryIndex = Arrays.binarySearch(nums, targetValue);
20
21 // If target not found, binarySearch returns -(insertion point) - 1
22 // Convert to actual insertion point (first element > nums[i] * k)
23 rightBoundaryIndex = rightBoundaryIndex < 0 ? -rightBoundaryIndex - 1 : rightBoundaryIndex;
24 }
25
26 // Update maximum valid subarray size
27 // rightBoundaryIndex - i gives the count of elements in range [nums[i], nums[i] * k]
28 maxValidSubarraySize = Math.max(maxValidSubarraySize, rightBoundaryIndex - i);
29 }
30
31 // Return minimum removals needed (total elements - maximum valid subarray)
32 return arrayLength - maxValidSubarraySize;
33 }
34}
35
1class Solution {
2public:
3 int minRemoval(vector<int>& nums, int k) {
4 // Sort the array to enable binary search and range calculations
5 ranges::sort(nums);
6
7 // Track the maximum number of elements that can form a valid subarray
8 int maxValidElements = 0;
9 int n = nums.size();
10
11 // For each element as the minimum value of a potential subarray
12 for (int i = 0; i < n; ++i) {
13 // Find the rightmost position where elements are still within k times the minimum
14 int rightBoundary = n;
15
16 // Check if the maximum possible value (nums[i] * k) is within array bounds
17 // Use 1LL to prevent integer overflow during multiplication
18 if (1LL * nums[i] * k <= nums[n - 1]) {
19 // Binary search for the first element greater than nums[i] * k
20 rightBoundary = upper_bound(nums.begin(), nums.end(), 1LL * nums[i] * k) - nums.begin();
21 }
22
23 // Update the maximum count of valid elements in a subarray
24 // The valid range is from index i to rightBoundary (exclusive)
25 maxValidElements = max(maxValidElements, rightBoundary - i);
26 }
27
28 // Return minimum removals needed (total elements minus maximum valid subarray)
29 return n - maxValidElements;
30 }
31};
32
1/**
2 * Finds the minimum number of elements to remove from an array
3 * such that the maximum element is at most k times the minimum element
4 * @param nums - The input array of numbers
5 * @param k - The multiplication factor constraint
6 * @returns The minimum number of elements to remove
7 */
8function minRemoval(nums: number[], k: number): number {
9 // Sort the array in ascending order
10 nums.sort((a: number, b: number) => a - b);
11
12 const arrayLength: number = nums.length;
13 let maxValidSubarraySize: number = 0;
14
15 // Try each element as the potential minimum of the valid subarray
16 for (let leftIndex: number = 0; leftIndex < arrayLength; ++leftIndex) {
17 let rightBoundIndex: number = arrayLength;
18
19 // Check if current minimum times k is less than the maximum element
20 if (nums[leftIndex] * k <= nums[arrayLength - 1]) {
21 // Find the upper bound index where elements exceed nums[leftIndex] * k
22 const threshold: number = nums[leftIndex] * k + 1;
23 rightBoundIndex = binarySearchUpperBound(nums, threshold);
24 }
25
26 // Update the maximum size of valid subarray found
27 maxValidSubarraySize = Math.max(maxValidSubarraySize, rightBoundIndex - leftIndex);
28 }
29
30 // Return the minimum number of elements to remove
31 return arrayLength - maxValidSubarraySize;
32}
33
34/**
35 * Binary search to find the index of the first element greater than or equal to target
36 * @param sortedArray - The sorted array to search in
37 * @param target - The target value to search for
38 * @returns The index of the first element >= target
39 */
40function binarySearchUpperBound(sortedArray: number[], target: number): number {
41 let left: number = 0;
42 let right: number = sortedArray.length;
43
44 while (left < right) {
45 const mid: number = Math.floor((left + right) / 2);
46 if (sortedArray[mid] < target) {
47 left = mid + 1;
48 } else {
49 right = mid;
50 }
51 }
52
53 return left;
54}
55
Time and Space Complexity
The time complexity is O(n × log n)
, where n
is the length of the array nums
. This complexity arises from two main operations:
- Sorting the array
nums.sort()
takesO(n × log n)
time - The for loop runs
n
times, and within each iteration,bisect_right()
performs a binary search which takesO(log n)
time, resulting inO(n × log n)
for the loop - The overall complexity is
O(n × log n) + O(n × log n) = O(n × log n)
The space complexity is O(log n)
, where n
is the length of the array nums
. This space is primarily used by:
- The sorting algorithm (typically Timsort in Python) which uses
O(log n)
space for its recursive call stack - The
bisect_right()
function usesO(1)
space as it performs iterative binary search - Variables
cnt
,i
,j
, andx
useO(1)
space - The overall space complexity is
O(log n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Integer Overflow When Computing k × min_value
The Problem: When calculating k * min_value
, the multiplication can cause integer overflow if both k
and min_value
are large integers. In Python, this isn't typically an issue due to arbitrary precision integers, but in languages like Java or C++, this can lead to incorrect results.
Example Scenario:
min_value = 10^9
k = 10^9
k * min_value = 10^18
(exceeds 32-bit integer range and approaches 64-bit limits)
Solution:
# Instead of direct multiplication, use comparison to avoid overflow
# Rather than: bisect_right(nums, k * min_value)
# Use a custom binary search with division:
def find_right_bound(nums, start_idx, k, min_value):
left, right = start_idx, len(nums)
while left < right:
mid = (left + right) // 2
# Avoid multiplication by using division
if nums[mid] <= k * min_value: # Could overflow
# Better: if nums[mid] / min_value <= k (but watch for division by zero)
# Or: if min_value == 0 or nums[mid] <= k * min_value
left = mid + 1
else:
right = mid
return left
2. Not Handling Edge Cases with k = 0
The Problem: When k = 0
, the only valid balanced array would be one where all elements are equal (since max ≤ 0 × min
only holds when max = 0
or when max = min = 0
).
Solution:
def minRemoval(self, nums: List[int], k: int) -> int:
if k == 0:
# Find the most frequent element and keep only those
from collections import Counter
counter = Counter(nums)
max_frequency = max(counter.values())
return len(nums) - max_frequency
# Continue with the original solution...
nums.sort()
max_elements_to_keep = 0
# ...
3. Misunderstanding the Binary Search Boundary
The Problem: Using bisect_left
instead of bisect_right
or misinterpreting the returned index. bisect_right
returns the index where we should insert to keep the array sorted, which is the first index where nums[index] > k * min_value
.
Incorrect Usage:
# Wrong: This finds elements < k * min_value, missing equal elements j = bisect_left(nums, k * min_value)
Correct Usage:
# Correct: This includes all elements <= k * min_value j = bisect_right(nums, k * min_value)
4. Not Considering Negative Numbers
The Problem: When the array contains negative numbers, the min-max relationship becomes tricky. If min_value
is negative and max_value
is positive, the condition max ≤ k × min
might behave unexpectedly.
Example:
nums = [-10, -5, 5]
,k = 2
- Using
-10
as min:5 ≤ 2 × (-10)
→5 ≤ -20
(false) - Using
-5
as min:5 ≤ 2 × (-5)
→5 ≤ -10
(false)
Solution: The original algorithm handles this correctly by trying each element as the minimum, but be aware that with negative numbers, you might need to keep only negative or only positive elements to achieve balance.
Which data structure is used in a depth first search?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster 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 the window The window
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
Want a Structured Path to Master System Design Too? Don’t Miss This!