diff --git a/DIRECTORY.md b/DIRECTORY.md index 032c1824..bd7b32f2 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -545,6 +545,8 @@ * [Test Cyclically Shifted Array](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/search/binary_search/cyclically_shifted_array/test_cyclically_shifted_array.py) * Find Closest Number * [Test Find Closest Number](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/search/binary_search/find_closest_number/test_find_closest_number.py) + * Find Closest Value + * [Test Find Closest Value](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/search/binary_search/find_closest_value/test_find_closest_value.py) * Find First In Duplicate List * [Test Find First In Duplicates](https://github.com/BrianLusina/PythonSnips/blob/master/puzzles/search/binary_search/find_first_in_duplicate_list/test_find_first_in_duplicates.py) * Find Fixed Number diff --git a/datastructures/trees/binary/search_tree/__init__.py b/datastructures/trees/binary/search_tree/__init__.py index 9c72583b..ac8a5cf1 100755 --- a/datastructures/trees/binary/search_tree/__init__.py +++ b/datastructures/trees/binary/search_tree/__init__.py @@ -219,6 +219,46 @@ def find_second_largest(self) -> BinaryTreeNode: return current + def find_closest_value_in_bst(self, target: T) -> Optional[BinaryTreeNode]: + """ + Finds the closest value in the binary search tree to the given target value. + + Args: + target T: Value to search for + Returns: + Node with the closest value to the target + """ + # edge case for empty nodes, if none is provided, we can't find a value that is close to the target + if not self.root: + return None + + # if the node's data is the target, exit early by returning it + if self.root.data == target: + return self.root + + # this keeps track of the minimum on both the left and the right + closest_node = self.root + min_diff = abs(target - self.root.data) + current = self.root + + # while the queue is not empty, we pop off nodes from the queue and check for their values + while current: + current_diff = abs(target - self.root.data) + + if current_diff < min_diff: + min_diff = current_diff + closest_node = current + + if current.data == target: + return current + + if target < current.data: + current = current.left + else: + current = current.right + + return closest_node + def range_sum(self, low: int, high: int): """ returns the sum of datas of all nodes with a data in the range [low, high]. diff --git a/puzzles/search/binary_search/find_closest_number/README.md b/puzzles/search/binary_search/find_closest_number/README.md index 1d19e0db..8dba2800 100644 --- a/puzzles/search/binary_search/find_closest_number/README.md +++ b/puzzles/search/binary_search/find_closest_number/README.md @@ -1,8 +1,7 @@ # Find the Closest Number -we will be given a sorted array and a target number. Our goal is to find a number in the array that is closest to the -target number. We will be making use of a binary search to solve this problem, so make sure that you have gone through -the previous lesson. +We will be given a sorted array and a target number. Our goal is to find a number in the array that is closest to the +target number. We will be making use of a binary search to solve this problem. The array may contain duplicate values and negative numbers. diff --git a/puzzles/search/binary_search/find_closest_value/README.md b/puzzles/search/binary_search/find_closest_value/README.md new file mode 100644 index 00000000..336e02b1 --- /dev/null +++ b/puzzles/search/binary_search/find_closest_value/README.md @@ -0,0 +1,43 @@ +# Find Closest Value in BST + +Write a function that takes in a Binary Search Tree (BST) and a target integer +value and returns the closest value to that target value contained in the BST. + +You can assume that there will only be one closest value. + +Each BST node has an integer value, a +left child node, and a right child node. A node is +said to be a valid BST node if and only if it satisfies the BST +property: its value is strictly greater than the values of every +node to its left; its value is less than or equal to the values +of every node to its right; and its children nodes are either valid +BST nodes themselves or None / null. + +Sample Input: + +```text +tree = 10 + / \ + 5 15 + / \ / \ + 2 5 13 22 + / \ +1 14 +target = 12 +``` + +Sample output: 13 + +## Hints + +- Try traversing the BST node by node, all the while keeping track of the node with the value closest to the target value. + Calculating the absolute value of the difference between a node's value and the target value should allow you to + check if that node is closer than the current closest one. +- Make use of the BST property to determine what side of any given node has values close to the target value and is + therefore worth exploring. +- What are the advantages and disadvantages of solving this problem iteratively as opposed to recursively? + +## Optimal Space & Time Complexity + +Average: O(log(n)) time | O(1) space where n is the number of nodes in the tree +BST Worst: O(n) time | O(1) space where n is the number of nodes in the tree diff --git a/puzzles/search/binary_search/find_closest_value/__init__.py b/puzzles/search/binary_search/find_closest_value/__init__.py new file mode 100644 index 00000000..80e5668c --- /dev/null +++ b/puzzles/search/binary_search/find_closest_value/__init__.py @@ -0,0 +1,35 @@ +from typing import Optional +from datastructures.trees.binary.search_tree import BinaryTreeNode + + +def find_closest_value_in_bst(node: BinaryTreeNode, target: int) -> Optional[int]: + # edge case for empty nodes, if none is provided, we can't find a value that is close to the target + if not node: + return None + + # if the node's data is the target, exit early by returning it + if node.data == target: + return node.data + + # this keeps track of the minimum on both the left and the right + closest_value = node.data + min_diff = abs(target - node.data) + current = node + + # while the queue is not empty, we pop off nodes from the queue and check for their values + while current: + current_diff = abs(target - current.data) + + if current_diff < min_diff: + min_diff = current_diff + closest_value = current.data + + if current.data == target: + return current.data + + if target < current.data: + current = current.left + else: + current = current.right + + return closest_value diff --git a/puzzles/search/binary_search/find_closest_value/test_find_closest_value.py b/puzzles/search/binary_search/find_closest_value/test_find_closest_value.py new file mode 100644 index 00000000..bc01c3cf --- /dev/null +++ b/puzzles/search/binary_search/find_closest_value/test_find_closest_value.py @@ -0,0 +1,31 @@ +import unittest +from datastructures.trees.binary.search_tree import BinaryTreeNode +from . import find_closest_value_in_bst + + +class FindClosestValueTestCases(unittest.TestCase): + def test_something(self): + root = BinaryTreeNode( + data=10, + left=BinaryTreeNode(data=5, + left=BinaryTreeNode( + data=2, + left=BinaryTreeNode(data=1), + right=BinaryTreeNode(data=5)) + ), + right=BinaryTreeNode(data=15, + left=BinaryTreeNode( + data=13, + right=BinaryTreeNode( + data=14, + right=BinaryTreeNode(data=22) + ) + )) + ) + expected = 13 + actual = find_closest_value_in_bst(root, target=12) + self.assertEqual(expected, actual) + + +if __name__ == '__main__': + unittest.main()