algorithms

Algorithm implementations
git clone git://git.laack.co/algorithms.git
Log | Files | Refs | README

commit 572deea60df2cd88ab7260b72bbdb9ed53899c0f
parent df6491e806461115e38795f271aa115dd36273f0
Author: Andrew Laack <andrew@laack.co>
Date:   Thu, 10 Sep 2026 20:01:04 -0500

Did an assortment of problems and revised segment tree to have O(logn) updates

Diffstat:
Acount-nodes-equal-to-average-of-subtree/count-nodes-equal-to-average-of-subtree.py | 32++++++++++++++++++++++++++++++++
Amice-and-cheese/mice-and-cheese.py | 33+++++++++++++++++++++++++++++++++
Mplan.txt | 2+-
Arange-sum-query-mutable/range-sum-query-mutable-v2.py | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aswap-nodes-in-pairs/swap.py | 29+++++++++++++++++++++++++++++
Aunique-three-digit-even-number/unique-three-digit-even-number.py | 26++++++++++++++++++++++++++
6 files changed, 218 insertions(+), 1 deletion(-)

diff --git a/count-nodes-equal-to-average-of-subtree/count-nodes-equal-to-average-of-subtree.py b/count-nodes-equal-to-average-of-subtree/count-nodes-equal-to-average-of-subtree.py @@ -0,0 +1,32 @@ +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right + +# idea + # backtracking + # average of this is sum of values / count of subtrees + # return this up the stack at each step + # have shared state that tracks count then return that + + +class Solution: + def recurse(self, nd): + if nd is None: + return (0,0) + left = self.recurse(nd.left) + right = self.recurse(nd.right) + + sum_val = (left[1] + right[1]) + nd.val + count = left[0] + right[0] + 1 + avg = sum_val // count + if avg == nd.val: + self.count += 1 + return (count, sum_val) + + def averageOfSubtree(self, root: TreeNode) -> int: + self.count = 0 + self.recurse(root) + return self.count diff --git a/mice-and-cheese/mice-and-cheese.py b/mice-and-cheese/mice-and-cheese.py @@ -0,0 +1,33 @@ +import heapq + +# two mice +# n types of cheese +# each type should be eaten by exactly one mouse +# reward1[i] if first mouse eats it +# reward2[i] if second mouse eats it +# k is non-negative, reward lists are all positive +# return max points if first mouse eats exactly k types of cheese + +# idea: + # seems greedy + # like what's the delta at each position + # then minimize this somehow + + # yes. Since first mouse eats only k cheese we compute the deltas and then select + # the ones that are largest wrt value of first eating - second eating + + # this'll be O(n + klogk) for time + # We could do O(logk) for space + +class Solution: + def miceAndCheese(self, reward1: List[int], reward2: List[int], k: int) -> int: + best_k_heap = [] + for i in range(len(reward1)): + delta = reward1[i] - reward2[i] + if len(best_k_heap) < k: + heapq.heappush(best_k_heap,delta) + else: + if k > 0 and best_k_heap[0] < delta: + heapq.heappop(best_k_heap) + heapq.heappush(best_k_heap,delta) + return sum(reward2) + sum(best_k_heap) diff --git a/plan.txt b/plan.txt @@ -5,4 +5,4 @@ these are the things I want to learn, ordered x prim's algo (visualized in python) x permutation problem (similar to interview where print all permutations of a string where only certain chars may be moved) x union find - - segment tree + x segment tree diff --git a/range-sum-query-mutable/range-sum-query-mutable-v2.py b/range-sum-query-mutable/range-sum-query-mutable-v2.py @@ -0,0 +1,97 @@ +class Node: + def __init__(self): + self.rng = [0, 0] + self.rv = 0 + self.left = None + self.right = None + +def combine(a,b): + root = Node() + root.left = a + root.right = b + root.rv = a.rv + b.rv + root.rng[0] = min(a.rng[0], b.rng[0]) + root.rng[1] = max(a.rng[1], b.rng[1]) + return root + + +# cases: + # if no overlap between rng and root return + # if same, return val + # if different return sum of left + right + +def find_sum(rng, root): + if root is None: + return 0 + if rng[1] < root.rng[0] or rng[0] > root.rng[1]: + return 0 + + if root.rng[0] >= rng[0] and root.rng[1] <= rng[1]: + return root.rv + + return find_sum(rng,root.left) + find_sum(rng,root.right) + +def update_val(root, idx, delta): + if root is None: + return + if idx < root.rng[0] or idx > root.rng[1]: + return + + root.rv += delta + update_val(root.right, idx, delta) + update_val(root.left, idx, delta) + + + + +def make_segment(nums): + forest = [] + + idx = 0 + for num in nums: + current = Node() + current.rng = [idx,idx] + current.rv = num + forest.append(current) + idx += 1 + + while len(forest) > 1: + nf = [] + + for i in range(0, len(forest), 2): + if i + 1 < len(forest): + nf.append(combine(forest[i], forest[i + 1])) + else: + nf.append(forest[i]) + + forest = nf + + return forest[0] + +class NumArray: + + # len(nums) is at most 30_000 + def __init__(self, nums: List[int]): + self.nums = nums + self.segment_tree = make_segment(nums) + + # nums[index] = val + def update(self, index: int, val: int) -> None: + # could do away with this list if we accept log(n) cost for lookups here + # this would retain the same WCTC of log(n) for updating but increase the constant. + # this would decrease memory usage, but still in O(n) for that too. + prior = self.nums[index] + self.nums[index] = val + update_val(self.segment_tree,index,val - prior) + return + + # range queries can be size of nums + # precondition: left <= right + # return: sum(nums[left,right]) - inclusive of left and right + def sumRange(self, left: int, right: int) -> int: + return find_sum([left,right], self.segment_tree) + +# Your NumArray object will be instantiated and called as such: +# obj = NumArray(nums) +# obj.update(index,val) +# param_2 = obj.sumRange(left,right) diff --git a/swap-nodes-in-pairs/swap.py b/swap-nodes-in-pairs/swap.py @@ -0,0 +1,29 @@ +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]: + + ls = [] + node1 = head + + while node1 != None: + ls.append(node1) + print(node1.val) + node1 = node1.next + + for i in range(0,len(ls) - 1, 2): + tmp = ls[i] + ls[i] = ls[i+1] + ls[i+1] = tmp + + for i in range(0,len(ls) - 1): + ls[i].next = ls[i+1] + + if len(ls) > 0: + ls[len(ls) - 1].next = None + head = ls[0] + + return head diff --git a/unique-three-digit-even-number/unique-three-digit-even-number.py b/unique-three-digit-even-number/unique-three-digit-even-number.py @@ -0,0 +1,26 @@ +class Solution: + def recurse(self, num_dict, depth, even_sel, has_num): + if depth == 3: + if even_sel: + return 1 + return 0 + summed = 0 + for num in num_dict: + count = num_dict[num] + if count == 0: + continue + if not has_num and num == 0: + continue + num_dict[num] -= 1 + summed += self.recurse(num_dict, depth + 1, num%2==0, True) + num_dict[num] += 1 + return summed + + def totalNumbers(self, digits: List[int]) -> int: + num_dict = {} + for num in digits: + if num_dict.get(num) is None: + num_dict[num] = 1 + else: + num_dict[num] += 1 + return self.recurse(num_dict, 0, False, False)