From da6252633720dfe8a8ca403425f2650708887b5c Mon Sep 17 00:00:00 2001 From: dhairya Date: Sat, 3 Oct 2026 03:17:25 +0530 Subject: [PATCH 1/3] Fix maximum Fenwick tree range values after updates --- data_structures/binary_tree/maximum_fenwick_tree.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data_structures/binary_tree/maximum_fenwick_tree.py b/data_structures/binary_tree/maximum_fenwick_tree.py index 84967a70cc73..7c71adfc2de9 100644 --- a/data_structures/binary_tree/maximum_fenwick_tree.py +++ b/data_structures/binary_tree/maximum_fenwick_tree.py @@ -81,7 +81,9 @@ def update(self, index: int, value: int) -> None: if current_left_border == index: self.tree[index] = value else: - self.tree[index] = max(value, current_left_border, index) + self.tree[index] = max( + self.arr[index], self.query(current_left_border, index) + ) index = self.get_next(index) def query(self, left: int, right: int) -> int: From d49aade77d8531ab39e15b0df111b95a0a6585b5 Mon Sep 17 00:00:00 2001 From: dhairya Date: Sat, 3 Oct 2026 09:26:18 +0530 Subject: [PATCH 2/3] Add Fenwick regression doctests and 2000-item benchmark Preserve all existing doctests while covering lost range maxima and decreases. Add an optional timeit benchmark for update and query workloads, as requested in TheAlgorithms/Python#15485. --- .../binary_tree/maximum_fenwick_tree.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/data_structures/binary_tree/maximum_fenwick_tree.py b/data_structures/binary_tree/maximum_fenwick_tree.py index 7c71adfc2de9..6bd2fffbaf0d 100644 --- a/data_structures/binary_tree/maximum_fenwick_tree.py +++ b/data_structures/binary_tree/maximum_fenwick_tree.py @@ -34,6 +34,26 @@ class MaxFenwickTree: >>> ft.update(0, 1000) >>> ft.query(0, 1) 1000 + + Updating a smaller sibling preserves the maximum, including after decreases. + >>> ft = MaxFenwickTree(8) + >>> ft.update(4, 20) + >>> ft.update(5, 1) + >>> ft.query(0, 6) + 20 + >>> ft.update(4, 0) + >>> ft.query(0, 6) + 1 + >>> ft.update(5, 0) + >>> ft.query(0, 6) + 0 + + Updating a child must also preserve the ancestor's own array value. + >>> ft = MaxFenwickTree(8) + >>> ft.update(5, 100) + >>> ft.update(4, 0) + >>> ft.query(0, 6) + 100 """ def __init__(self, size: int) -> None: @@ -112,5 +132,41 @@ def query(self, left: int, right: int) -> int: if __name__ == "__main__": import doctest + import sys + from timeit import repeat doctest.testmod() + + # Run with --benchmark on each revision to compare the same 2,000-item workload. + if "--benchmark" in sys.argv: + size = 2000 + values = [(index * 97) % size for index in range(size)] + updates = [(index, (index * 37) % size) for index in range(size)] + updates += [(index, values[index]) for index in reversed(range(size))] + queries = [(left, size) for left in range(size)] + + # Build correct query buckets outside the timer, even on the buggy revision. + query_setup = """ +tree = MaxFenwickTree(size) +tree.arr = values[:] +tree.tree = [ + max(values[tree.get_prev(index) + 1 : index + 1]) + for index in range(size) +] +""" + for operation, statement, setup in ( + ( + "4000 updates", + "for index, value in updates: tree.update(index, value)", + "tree = MaxFenwickTree(size)", + ), + ( + "2000 queries", + "for left, right in queries: tree.query(left, right)", + query_setup, + ), + ): + timings = repeat( + statement, setup=setup, repeat=5, number=1, globals=globals() + ) + print(f"{size} items, {operation}: {min(timings):.6f} seconds (best of 5)") From c25e1e9e3f81407e3f2205a842b0f2907aad8b94 Mon Sep 17 00:00:00 2001 From: dhairya Date: Sat, 3 Oct 2026 12:08:19 +0530 Subject: [PATCH 3/3] Avoid unnecessary maximum Fenwick recomputation Propagate only changed maxima and rebuild necessary buckets directly from disjoint child buckets. Preserve arbitrary overwrites, regression doctests and the benchmark. --- .../binary_tree/maximum_fenwick_tree.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/data_structures/binary_tree/maximum_fenwick_tree.py b/data_structures/binary_tree/maximum_fenwick_tree.py index 6bd2fffbaf0d..fd6351e288dd 100644 --- a/data_structures/binary_tree/maximum_fenwick_tree.py +++ b/data_structures/binary_tree/maximum_fenwick_tree.py @@ -95,15 +95,32 @@ def update(self, index: int, value: int) -> None: Returns: None """ + old_value = self.arr[index] + if value == old_value: + return self.arr[index] = value while index < self.size: - current_left_border = self.get_prev(index) + 1 - if current_left_border == index: + old_maximum = self.tree[index] + if value > old_maximum: self.tree[index] = value + elif old_value == old_maximum: + current_left_border = self.get_prev(index) + 1 + maximum = self.arr[index] + if current_left_border != index: + maximum = max(0, maximum) + child = index - 1 + # These disjoint child buckets cover the rest of this bucket. + while child >= current_left_border: + maximum = max(maximum, self.tree[child]) + if maximum == old_maximum: + break + child = self.get_prev(child) + self.tree[index] = maximum + if maximum == old_maximum: + break else: - self.tree[index] = max( - self.arr[index], self.query(current_left_border, index) - ) + # An unchanged bucket maximum leaves all its ancestors unchanged. + break index = self.get_next(index) def query(self, left: int, right: int) -> int: