You are given a 0-indexed integer array nums
. An index i
is part of a hill in nums
if the closest non-equal neighbors of i
are smaller than nums[i]
. Similarly, an index i
is part of a valley in nums
if the closest non-equal neighbors of i
are larger than nums[i]
. Adjacent indices i
and j
are part of the same hill or valley if nums[i] == nums[j]
.
Note that for an index to be part of a hill or valley, it must have a non-equal neighbor on both the left and right of the index.
Return the number of hills and valleys in nums
.
For example:
nums = [2,4,1,1,6,5]
Output: 3
Explanation:
There are 3 hills and valleys so we return 3.
Could you provide an efficient solution to determine the number of hills and valleys in a given array, taking into account the specified conditions and constraints?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Imagine you're looking at a landscape made of blocks, some taller and some shorter. To find the hills and valleys the most straightforward way is to look at each block one by one and compare it to its neighbors.
Here's how the algorithm would work step-by-step:
def count_hills_and_valleys_brute_force(blocks):
number_of_hills_and_valleys = 0
# We skip the first and last elements.
for index in range(1, len(blocks) - 1):
# Compare current block with its neighbors
previous_block = blocks[index - 1]
current_block = blocks[index]
next_block = blocks[index + 1]
# Check for hill condition
if current_block > previous_block and current_block > next_block:
number_of_hills_and_valleys += 1
# Check for valley condition
elif current_block < previous_block and current_block < next_block:
number_of_hills_and_valleys += 1
return number_of_hills_and_valleys
To efficiently count hills and valleys, we only need to compare each number to its neighbors. The key is to ignore flat plateaus and treat them as part of either a hill or a valley depending on the surrounding numbers.
Here's how the algorithm would work step-by-step:
def count_hills_and_valleys(numbers):
number_of_hills_and_valleys = 0
length_of_numbers = len(numbers)
for i in range(1, length_of_numbers - 1):
left_index = i - 1
right_index = i + 1
# Find the closest different number on the left
while left_index >= 0 and numbers[left_index] == numbers[i]:
left_index -= 1
# Find the closest different number on the right
while right_index < length_of_numbers and numbers[right_index] == numbers[i]:
right_index += 1
# If either boundary goes out of range, it's not a hill/valley
if left_index < 0 or right_index >= length_of_numbers:
continue
#Determine if current number is hill or valley
if numbers[i] > numbers[left_index] and numbers[i] > numbers[right_index]:
number_of_hills_and_valleys += 1
if numbers[i] < numbers[left_index] and numbers[i] < numbers[right_index]:
number_of_hills_and_valleys += 1
return number_of_hills_and_valleys
Case | How to Handle |
---|---|
Empty or null input array | Return 0, as there are no hills or valleys in an empty array. |
Array with fewer than 3 elements | Return 0, as a hill or valley requires a peak/trough surrounded by two neighbors. |
Array with all identical values | Return 0, because no element strictly satisfies the hill/valley condition when all neighbors are equal. |
Consecutive duplicate values | Skip over consecutive duplicate values to find the next distinct neighbor for comparison. |
Integer overflow during calculations if values are extremely large | Ensure to use appropriate data types (e.g., long) to prevent overflow during comparison. |
Alternating high and low values (e.g., sawtooth pattern) | The algorithm should correctly identify each peak and trough as a hill or valley, respectively. |
Input array containing negative numbers and/or zero | The comparison logic should work correctly regardless of whether the numbers are positive, negative, or zero. |
Very large array sizes exceeding available memory | Ensure algorithm complexity stays linear to prevent excessive memory allocation errors. |