From 57fad825c8468cb5a3fa16fe1d3c28acb6b21d59 Mon Sep 17 00:00:00 2001 From: Lusina <12752833+BrianLusina@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:12:41 +0300 Subject: [PATCH 1/3] feat(puzzles, datastructures): find closest value in bst --- .../trees/binary/search_tree/__init__.py | 46 +++++++++++++++++++ .../find_closest_number/README.md | 5 +- .../find_closest_value/README.md | 43 +++++++++++++++++ .../find_closest_value/__init__.py | 42 +++++++++++++++++ .../test_find_closest_value.py | 31 +++++++++++++ 5 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 puzzles/search/binary_search/find_closest_value/README.md create mode 100644 puzzles/search/binary_search/find_closest_value/__init__.py create mode 100644 puzzles/search/binary_search/find_closest_value/test_find_closest_value.py diff --git a/datastructures/trees/binary/search_tree/__init__.py b/datastructures/trees/binary/search_tree/__init__.py index 9c72583b..74bd0676 100755 --- a/datastructures/trees/binary/search_tree/__init__.py +++ b/datastructures/trees/binary/search_tree/__init__.py @@ -219,6 +219,52 @@ 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 + min_diff = min_diff_left = min_diff_right = float("inf") + closest_value = None + fifo_queue = FifoQueue() + fifo_queue.enqueue(self.root) + + # while the queue is not empty, we pop off nodes from the queue and check for their values + while not fifo_queue.is_empty(): + current_node = fifo_queue.dequeue() + + min_diff_left = abs(target - current_node.data) + min_diff_right = abs(target - current_node.data) + + if min_diff_left < min_diff: + min_diff = min_diff_left + closest_value = current_node + + if min_diff_right < min_diff: + min_diff = min_diff_right + closest_value = current_node + + if current_node.left: + fifo_queue.enqueue(current_node.left) + + if current_node.right: + fifo_queue.enqueue(current_node.right) + + return closest_value + 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..50839a5f --- /dev/null +++ b/puzzles/search/binary_search/find_closest_value/__init__.py @@ -0,0 +1,42 @@ +from typing import Optional +from queue import Queue +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 + min_diff = min_diff_left = min_diff_right = float("inf") + closest_value = None + fifo_queue = Queue() + fifo_queue.put(node) + + # while the queue is not empty, we pop off nodes from the queue and check for their values + while not fifo_queue.empty(): + current_node = fifo_queue.get() + + min_diff_left = abs(target - current_node.data) + min_diff_right = abs(target - current_node.data) + + if min_diff_left < min_diff: + min_diff = min_diff_left + closest_value = current_node.data + + if min_diff_right < min_diff: + min_diff = min_diff_right + closest_value = current_node.data + + if current_node.left: + fifo_queue.put(current_node.left) + + if current_node.right: + fifo_queue.put(current_node.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() From 1d8d3773da3745cf472268457e5ecbc75609ed62 Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:13:02 +0000 Subject: [PATCH 2/3] updating DIRECTORY.md --- DIRECTORY.md | 2 ++ 1 file changed, 2 insertions(+) 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 From fb749bd29115a95e17daaf22a9419ddfc0e8a0c7 Mon Sep 17 00:00:00 2001 From: Lusina <12752833+BrianLusina@users.noreply.github.com> Date: Wed, 29 Oct 2025 12:06:47 +0300 Subject: [PATCH 3/3] refactor(datastructures, binary-search-tree, puzzles): remove the use of a queue in find closed value --- .../trees/binary/search_tree/__init__.py | 36 ++++++++----------- .../find_closest_value/__init__.py | 35 ++++++++---------- 2 files changed, 29 insertions(+), 42 deletions(-) diff --git a/datastructures/trees/binary/search_tree/__init__.py b/datastructures/trees/binary/search_tree/__init__.py index 74bd0676..ac8a5cf1 100755 --- a/datastructures/trees/binary/search_tree/__init__.py +++ b/datastructures/trees/binary/search_tree/__init__.py @@ -237,33 +237,27 @@ def find_closest_value_in_bst(self, target: T) -> Optional[BinaryTreeNode]: return self.root # this keeps track of the minimum on both the left and the right - min_diff = min_diff_left = min_diff_right = float("inf") - closest_value = None - fifo_queue = FifoQueue() - fifo_queue.enqueue(self.root) + 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 not fifo_queue.is_empty(): - current_node = fifo_queue.dequeue() - - min_diff_left = abs(target - current_node.data) - min_diff_right = abs(target - current_node.data) - - if min_diff_left < min_diff: - min_diff = min_diff_left - closest_value = current_node + while current: + current_diff = abs(target - self.root.data) - if min_diff_right < min_diff: - min_diff = min_diff_right - closest_value = current_node + if current_diff < min_diff: + min_diff = current_diff + closest_node = current - if current_node.left: - fifo_queue.enqueue(current_node.left) + if current.data == target: + return current - if current_node.right: - fifo_queue.enqueue(current_node.right) + if target < current.data: + current = current.left + else: + current = current.right - return closest_value + return closest_node def range_sum(self, low: int, high: int): """ diff --git a/puzzles/search/binary_search/find_closest_value/__init__.py b/puzzles/search/binary_search/find_closest_value/__init__.py index 50839a5f..80e5668c 100644 --- a/puzzles/search/binary_search/find_closest_value/__init__.py +++ b/puzzles/search/binary_search/find_closest_value/__init__.py @@ -1,5 +1,4 @@ from typing import Optional -from queue import Queue from datastructures.trees.binary.search_tree import BinaryTreeNode @@ -13,30 +12,24 @@ def find_closest_value_in_bst(node: BinaryTreeNode, target: int) -> Optional[int return node.data # this keeps track of the minimum on both the left and the right - min_diff = min_diff_left = min_diff_right = float("inf") - closest_value = None - fifo_queue = Queue() - fifo_queue.put(node) + 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 not fifo_queue.empty(): - current_node = fifo_queue.get() + while current: + current_diff = abs(target - current.data) - min_diff_left = abs(target - current_node.data) - min_diff_right = abs(target - current_node.data) + if current_diff < min_diff: + min_diff = current_diff + closest_value = current.data - if min_diff_left < min_diff: - min_diff = min_diff_left - closest_value = current_node.data + if current.data == target: + return current.data - if min_diff_right < min_diff: - min_diff = min_diff_right - closest_value = current_node.data - - if current_node.left: - fifo_queue.put(current_node.left) - - if current_node.right: - fifo_queue.put(current_node.right) + if target < current.data: + current = current.left + else: + current = current.right return closest_value