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