In this assignment, we'll practice the techniques learned so far in this lesson with the "Minimum Count" problem.
// Given an array `nums` sorted in ascending order, determine
// the minimum between the count of positive integers and the
// count of negative integers.
// Please note that the number `0` is neither positive nor negative.
console.log(minimumCount([-4, -3, -2, -1, 3, 4]) === 2);
console.log(minimumCount([-3, 1, 2, 3, 4, 5]) === 1);
console.log(minimumCount([-5, -4, -3, -2, -1]) === 0);
console.log(minimumCount([1, 2, 3, 4, 5]) === 0);
console.log(minimumCount([-2, -1, 1, 2]) === 2);
console.log(minimumCount([-7, -5, -4, 1, 2, 6, 10]) === 3);
console.log(minimumCount([-3, -2, -1, 0, 5, 6]) === 2);
console.log(minimumCount([-1, 0, 1]) === 1);
console.log(minimumCount([]) === 0);
// All test cases should log true.
We'll need to use two binary searches for this problem. At a glance, it may seem like we could just find where our array switches from negative numbers to positive with a single search, but because zero is neither negative nor positive, this implementation would not solve an array with one or more zeros.
As such, we'll use one binary search to find where our negative numbers end from the beginning of the array and another binary search to find where our positive numbers end from the end of our array. Remember, binary search problems depend on the array being sorted; this problem is no exception.
Once we know where our negatives stop and our positives start, we can use those indices to calculate the count of each and return the smaller of the two counts.
We'll create three functions to break our problem into smaller pieces: minimumCount
, findFirstPositiveIndex
, and findLastNegativeIndex
.
The findFirstPositiveIndex
function utilizes a binary search algorithm to find the first positive number in the array:
left
and right
, representing the array's start and end, respectively.
firstPositiveIdx
to the length of the array, which assumes that all numbers could be negative until proven otherwise.
mid
.
nums[mid]
is less than or equal to zero.
left
to mid + 1
.
firstPositiveIdx
to the value of mid
and continue searching the left half of the array by setting right to mid - 1
.
left
exceeds right
. At this point, firstPostiveIdx
will point to the first positive number, or it will have never been reassigned and remain as the length of the array, meaning all numbers were negative.
Conversely, findLastNegativeIndex
seeks the index of the last negative number in the array, using a similar binary search approach. It starts with left
and right
pointers and iteratively finds the last index where a negative number exists, denoted by lastNegativeIdx
. If the array only contains positive numbers, the function returns -1
.
Finally, we will use our third function, minimumCount
to utilize our first two functions and find the smaller count:
lastNegativeIdx
from findLastNegativeIndex
. The count of negative numbers is the lastNegativeIdx
+ 1 since indices are zero-based.
firstPositiveIdx
from findFirstPositiveIndex
. The count of positive numbers is the total number of elements in the array minus firstPositiveIdx
.
The time complexity of this solution is similar to that of the "Find The Ranges of Three" problem. Because we perform two binary searches, our time complexity is O(2logN)
. However, we can simplify it to O(logN)
by eliminating the constants.
The space complexity of this algorithm is O(1)
. The functions don't utilize any extra data structures that grow with the size of the input.
Let's see this visually using an example array [-3, 1, 2, 3, 4, 5]
.
Step 1: In the first step, we are initializing two pointers, left
and right
, at indices 0
and 5
respectively, and the firstPositiveIdx
to 6
, assuming that all numbers in the array are negative until proven otherwise.
Step 2: In the second step, we calculate the middle index using the formula ((0 + 5) / 2)
, which results in 2
rounded down. Since the number at this index is positive, we reassign firstPositiveIdx
to 2
, and move to the left, by reassigning right
to mid - 1
.
Step 3: In the third step, we calculate the middle index once again using the formula ((0 + 1) / 2)
, which results in 0
when rounded down. Since the number at index 0
is negative, we move to the right by reassigning left
to mid + 1
. The firstPositiveIdx
remains at 2
.
Step 4: In the fourth and final step, the middle index is calculated using the formula ((1 + 1) / 2)
which results in 1
. Since the number at index 1
is positive, we reassign firstPositiveIndex
to 1
, and move to the left by reassigning right
to mid - 1
. The left
pointer becomes greater than the right
, which ends the loop, and we return the value at firstPositiveIdx
, which is 1
.
Step 1: In the first step, we are initializing two pointers, left
and right
, at indices 0
and 5
respectively, and the lastNegativeIdx
to -1
, assuming that all numbers in the array are positive until proven otherwise.
Step 2: In the second step, we calculate the middle index using the formula ((0 + 5) / 2)
, which results in 2
rounded down. Since the number is positive, we move to the left, by reassigning right
to mid - 1
.
Step 3: In the third step, we calculate the middle index once again using the formula ((0 + 1) / 2)
, which results in 0
when rounded down. Since the number at index 0
is negative, we reassign lastNegativeIdx
to 0
and move to the right by reassigning left
to mid + 1
.
Step 4: In the fourth and final step, the middle index is calculated using the formula ((1 + 1) / 2)
which results in 1
. Since the number at index 1
is positive, we move to the left reassigning right
to mid - 1
. The left
pointer becomes greater than the right
, which ends the loop, and we return the value at lastNegativeIdx
, which is 0
.
With both of our searches complete, we can now use these values within our main minimumCount
function that you can find below.
function minimumCount(nums) {
const firstPositiveIdx = findFirstPositiveIndex(nums);
const lastNegativeIdx = findLastNegativeIndex(nums);
const negativeCount = lastNegativeIdx + 1;
const positiveCount = nums.length - firstPositiveIdx;
return Math.min(negativeCount, positiveCount);
}
function findFirstPositiveIndex(nums) {
let left = 0;
let right = nums.length - 1;
let firstPositiveIdx = nums.length;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] <= 0) {
left = mid + 1;
} else {
firstPositiveIdx = mid;
right = mid - 1;
}
}
return firstPositiveIdx;
}
function findLastNegativeIndex(nums) {
let left = 0;
let right = nums.length - 1;
let lastNegativeIdx = -1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] < 0) {
lastNegativeIdx = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return lastNegativeIdx;
}