Skip to content

Commit

Permalink
feat(binary search): find peak element
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianLusina committed Oct 19, 2023
1 parent 032aaa9 commit 0d70833
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 6 deletions.
29 changes: 29 additions & 0 deletions puzzles/search/binary_search/find_peak_element/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Find Peak Element

A peak element is an element that is strictly greater than its neighbors.

Given a 0-indexed integer array nums, find a peak element, and return its index. If the array contains multiple peaks,
return the index to any of the peaks.

You may imagine that nums[-1] = nums[n] = -∞. In other words, an element is always considered to be strictly greater
than a neighbor that is outside the array.

You must write an algorithm that runs in O(log n) time.

```plain
Example 1:
Input: nums = [1,2,3,1]
Output: 2
Explanation: 3 is a peak element and your function should return the index number 2.
Example 2:
Input: nums = [1,2,1,3,5,6,4]
Output: 5
Explanation: Your function can return either index number 1 where the peak element is 2, or index number 5 where the peak element is 6.
```

## Topics

- Array
- Binary Search
43 changes: 43 additions & 0 deletions puzzles/search/binary_search/find_peak_element/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import List


def find_peak_element(nums: List[int]) -> int:
"""Finds a peak element's index in the provided list of integers
Algorithm:
- Initializes left as the start index of the list and right as the end index of the list (len(nums)- 1).
- Perform binary search until left becomes equal to right.
- Calculate the middle index mid using the formula mid = left + (right - left) // 2. or mid = (left + right) >> 1
- Compare nums[mid] with nums[mid + 1] to determine if the peak is on the left side or the right side of mid.
- If nums[mid] is greater than nums[mid + 1], move the right index to mid, indicating that the peak is on the
left side.
- Otherwise, move the left index to mid + 1, indicating that the peak is on the right side.
- Repeat steps 3-4 until left becomes equal to right.
- Return the value of peak_index, which represents the index of the peak element.
Complexity:
- Time complexity O(log n): Where n is the number of elements in the nums vector.
- Space Complexity O(1): Since it uses a constant amount of extra space.
Args:
nums (List): list of integers
Returns:
int: index of a peak element(i.e. element that is greater than its adjacent neighbours)
"""
left = 0
right = len(nums) - 1
peak_index = -1

while left <= right:
mid = (left + right) >> 1

# check that the potential peak element is within bounds or is greater than both its neighbours
potential_peak = nums[mid]
if mid == len(nums) - 1 or potential_peak > nums[mid + 1]:
peak_index = mid
right = mid - 1
else:
left = mid + 1

return peak_index
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import unittest
from . import find_peak_element


class FindPeakElementTestCase(unittest.TestCase):
def test_1(self):
"""should return 2 for nums = [1,2,3,1]"""
nums = [1, 2, 3, 1]
expected = 2
actual = find_peak_element(nums)
self.assertEqual(expected, actual)

def test_2(self):
"""should return either 5 or 1 for nums = [1,2,1,3,5,6,4]"""
nums = [1, 2, 1, 3, 5, 6, 4]
expected_5 = 5
expected_1 = 1
actual = find_peak_element(nums)
self.assertIn(actual, [expected_1, expected_5])

def test_3(self):
"""should return either 5 or 1 for nums = [1,2,1,3,5,6,4]"""
nums = [0, 1, 2, 3, 2, 1, 0]
expected = 3
actual = find_peak_element(nums)
self.assertEqual(actual, expected)

def test_4(self):
"""should return 1 from 0 10 3 2 1 0"""
nums = [0, 10, 3, 2, 1, 0]
expected = 1
actual = find_peak_element(nums)
self.assertEqual(expected, actual)

def test_5(self):
"""should return 1 from 0 10 0"""
nums = [0, 10, 0]
expected = 1
actual = find_peak_element(nums)
self.assertEqual(expected, actual)

def test_6(self):
"""should return 16 from 0 1 2 12 22 32 42 52 62 72 82 92 102 112 122 132 133 132 111 0"""
nums = [0, 1, 2, 12, 22, 32, 42, 52, 62, 72, 82, 92, 102, 112, 122, 132, 133, 132, 111, 0]
expected = 16
actual = find_peak_element(nums)
self.assertEqual(expected, actual)

def test_7(self):
"""should return 1 from 0, 10, 5, 2"""
nums = [0, 10, 5, 2]
expected = 1
actual = find_peak_element(nums)
self.assertEqual(expected, actual)

def test_8(self):
"""should return 1 from 0, 2, 1, 0"""
nums = [0, 2, 1, 0]
expected = 1
actual = find_peak_element(nums)
self.assertEqual(expected, actual)

def test_9(self):
"""should return 1 from 0, 1, 0"""
nums = [0, 1, 0]
expected = 1
actual = find_peak_element(nums)
self.assertEqual(expected, actual)


if __name__ == '__main__':
unittest.main()
9 changes: 3 additions & 6 deletions puzzles/search/binary_search/peak_of_mountain/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
# The Peak of a Mountain Array

A mountain array is defined as an array that

has at least 3 elements
has an element with the largest value called "peak", with index k. The array elements strictly increase from the first
element to A[k], and then strictly decreases from A[k + 1] to the last element of the array. Thus creating a "mountain"
of numbers.
A mountain array is defined as an array that has at least 3 elements has an element with the largest value called "
peak", with index k. The array elements strictly increase from the first element to A[k], and then strictly decreases
from A[k + 1] to the last element of the array. Thus creating a "mountain" of numbers.
That is, given A[0]<...<A[k-1]<A[k]>A[k+1]>...>A[n-1], we need to find the index k. Note that the peak element is
neither the first nor the last element of the array.

Expand Down

0 comments on commit 0d70833

Please sign in to comment.