From 76bff11d7a32cff1b4c064e93f72d29d8c9ae83c Mon Sep 17 00:00:00 2001 From: mathusanm6 Date: Wed, 3 Sep 2025 08:59:40 +0200 Subject: [PATCH] feat: add problem minimum-flips-in-binary-tree-to-get-result with unit tests --- README.md | 26 +++-- common/.gitkeep | 0 common/__init__.py | 3 + common/trees/treenode.h | 14 +++ common/trees/treenode.py | 8 ++ config/tags.yml | 20 ++-- .../config.yml | 17 ++++ ...imum_flips_in_binary_tree_to_get_result.cc | 39 ++++++++ ...nimum_flips_in_binary_tree_to_get_result.h | 3 + ...imum_flips_in_binary_tree_to_get_result.py | 46 +++++++++ ...flips_in_binary_tree_to_get_result_test.cc | 99 +++++++++++++++++++ ...flips_in_binary_tree_to_get_result_test.py | 84 ++++++++++++++++ 12 files changed, 341 insertions(+), 18 deletions(-) delete mode 100644 common/.gitkeep create mode 100644 common/__init__.py create mode 100644 common/trees/treenode.h create mode 100644 common/trees/treenode.py create mode 100644 problems/minimum_flips_in_binary_tree_to_get_result/config.yml create mode 100644 problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc create mode 100644 problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.h create mode 100644 problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py create mode 100644 problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.cc create mode 100644 problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.py diff --git a/README.md b/README.md index e194b03..9e20cd4 100644 --- a/README.md +++ b/README.md @@ -164,18 +164,20 @@ This repository covers a comprehensive range of algorithmic patterns and data st - [Two Pointers](#two-pointers) - [Sliding Window](#sliding-window) - [Stack](#stack) -- [Matrix](#matrix) -- [Intervals](#intervals) +- [Binary Search](#binary-search) - [Linked List](#linked-list) -- [Binary Tree General](#binary-tree-general) -- [Binary Tree BFS](#binary-tree-bfs) -- [Binary Search Tree](#binary-search-tree) -- [Graph General](#graph-general) -- [Dynamic Programming](#dynamic-programming) +- [Trees](#trees) +- [Heap / Priority Queue](#heap-/-priority-queue) - [Backtracking](#backtracking) -- [Heap](#heap) +- [Tries](#tries) +- [Graphs](#graphs) +- [Advanced Graphs](#advanced-graphs) +- [1-D Dynamic Programming](#1-d-dynamic-programming) +- [2-D Dynamic Programming](#2-d-dynamic-programming) - [Greedy](#greedy) -- [Trie](#trie) +- [Intervals](#intervals) +- [Math & Geometry](#math--geometry) +- [Bit Manipulation](#bit-manipulation) ## Arrays & Hashing @@ -189,3 +191,9 @@ This repository covers a comprehensive range of algorithmic patterns and data st | # | Title | Solution | Time | Space | Difficulty | Tag | Note | |---|-------|----------|------|-------|------------|-----|------| | 125 | [Valid Palindrome](https://leetcode.com/problems/valid-palindrome/) | [Python](./problems/valid_palindrome/valid_palindrome.py), [C++](./problems/valid_palindrome/valid_palindrome.cc) | _O(n)_ | _O(1)_ | Easy | | | + +## Trees + +| # | Title | Solution | Time | Space | Difficulty | Tag | Note | +|---|-------|----------|------|-------|------------|-----|------| +| 2313 | [Minimum Flips in Binary Tree to Get Result](https://leetcode.com/problems/minimum-flips-in-binary-tree-to-get-result/description/) | [Python](./problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py), [C++](./problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc) | _O(n)_ | _O(1)_ | Hard | | _n_ is the number of nodes in the binary tree. | diff --git a/common/.gitkeep b/common/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 0000000..a31855b --- /dev/null +++ b/common/__init__.py @@ -0,0 +1,3 @@ +from .trees.treenode import TreeNode + +__all__ = ["TreeNode"] diff --git a/common/trees/treenode.h b/common/trees/treenode.h new file mode 100644 index 0000000..b996e39 --- /dev/null +++ b/common/trees/treenode.h @@ -0,0 +1,14 @@ +#ifndef COMMON_TREES_TREENODE_H +#define COMMON_TREES_TREENODE_H + +// Definition for a binary tree node. +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; + TreeNode() : val(0), left(nullptr), right(nullptr) {} + TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} +}; + +#endif // COMMON_TREES_TREENODE_H \ No newline at end of file diff --git a/common/trees/treenode.py b/common/trees/treenode.py new file mode 100644 index 0000000..f35de0c --- /dev/null +++ b/common/trees/treenode.py @@ -0,0 +1,8 @@ +"""Common tree node definition.""" + +# 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 diff --git a/config/tags.yml b/config/tags.yml index fc77d5c..0a921c6 100644 --- a/config/tags.yml +++ b/config/tags.yml @@ -3,15 +3,17 @@ tags: - "Two Pointers" - "Sliding Window" - "Stack" - - "Matrix" - - "Intervals" + - "Binary Search" - "Linked List" - - "Binary Tree General" - - "Binary Tree BFS" - - "Binary Search Tree" - - "Graph General" - - "Dynamic Programming" + - "Trees" + - "Heap / Priority Queue" - "Backtracking" - - "Heap" + - "Tries" + - "Graphs" + - "Advanced Graphs" + - "1-D Dynamic Programming" + - "2-D Dynamic Programming" - "Greedy" - - "Trie" + - "Intervals" + - "Math & Geometry" + - "Bit Manipulation" diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/config.yml b/problems/minimum_flips_in_binary_tree_to_get_result/config.yml new file mode 100644 index 0000000..b5d0e0b --- /dev/null +++ b/problems/minimum_flips_in_binary_tree_to_get_result/config.yml @@ -0,0 +1,17 @@ +problem: + number: 2313 + title: "Minimum Flips in Binary Tree to Get Result" + leetcode_url: "https://leetcode.com/problems/minimum-flips-in-binary-tree-to-get-result/description/" + difficulty: "hard" + tags: ["Trees"] + +solutions: + python: "problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py" + cpp: "problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc" + +complexity: + time: "O(n)" + space: "O(1)" + +notes: "_n_ is the number of nodes in the binary tree." +readme_link: "" diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc new file mode 100644 index 0000000..c35d596 --- /dev/null +++ b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.cc @@ -0,0 +1,39 @@ +#include "minimum_flips_in_binary_tree_to_get_result.h" + +#include +#include +#include +#include "../../common/trees/treenode.h" + +using namespace std; + +int minimumFlips(TreeNode* root, bool result) { + function(TreeNode*)> dfs = [&](TreeNode* node) -> pair { + if (!node) { + return {1 << 30, 1 << 30}; // Impossible case + } + const int x = node->val; + if (x >= 0 && x <= 1) { + return {x == 0 ? 0 : 1, x == 1 ? 0 : 1}; + } + + auto [left_false, left_true] = dfs(node->left); + auto [right_false, right_true] = dfs(node->right); + if (x == 2) { // OR + return {left_false + right_false, min({left_true + right_true, left_true + right_false, + left_false + right_true})}; + } else if (x == 3) { // AND + return { + min({left_false + right_false, left_true + right_false, left_false + right_true}), + left_true + right_true}; + } else if (x == 4) { // XOR + return {min({left_false + right_false, left_true + right_true}), + min({left_true + right_false, left_false + right_true})}; + } else if (x == 5) { // NOT + return {left_true, left_false}; + } else { + return {1 << 30, 1 << 30}; // Invalid operation + } + }; + return result ? dfs(root).second : dfs(root).first; +} \ No newline at end of file diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.h b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.h new file mode 100644 index 0000000..fd04fb1 --- /dev/null +++ b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.h @@ -0,0 +1,3 @@ +#include "../../common/trees/treenode.h" + +int minimumFlips(TreeNode* root, bool result); \ No newline at end of file diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py new file mode 100644 index 0000000..1f216b6 --- /dev/null +++ b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result.py @@ -0,0 +1,46 @@ +from common import TreeNode +from math import inf +from typing import Optional, Tuple + + +def minimumFlips(root: Optional[TreeNode], result: bool) -> int: + def dfs(node: Optional[TreeNode]) -> Tuple[int, int]: + """Returns a tuple (min_flips_to_false, min_flips_to_true)""" + if node is None: + return inf, inf + + x = node.val + if x in (0, 1): + return (1, 0) if x == 1 else (0, 1) + + left_false, left_true = dfs(node.left) + right_false, right_true = dfs(node.right) + if x == 2: # OR + return ( + left_false + right_false, + min( + left_true + right_false, + left_false + right_true, + left_true + right_true, + ), + ) + elif x == 3: # AND + return ( + min( + left_false + right_false, + left_false + right_true, + left_true + right_false, + ), + left_true + right_true, + ) + elif x == 4: # XOR + return ( + min(left_false + right_false, left_true + right_true), + min(left_false + right_true, left_true + right_false), + ) + elif x == 5: # NOT + return (left_true, left_false) + else: + raise ValueError(f"Unknown operation: {x}") + + return dfs(root)[int(result)] diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.cc b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.cc new file mode 100644 index 0000000..5b2f5b7 --- /dev/null +++ b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.cc @@ -0,0 +1,99 @@ +#include "minimum_flips_in_binary_tree_to_get_result.h" + +#include +#include +#include "../../common/trees/treenode.h" + +struct MinimumFlipsCase { + std::string test_name; + TreeNode *root; + bool result; + int expected; +}; + +using MinimumFlipsTest = ::testing::TestWithParam; + +TEST_P(MinimumFlipsTest, TestCases) { + const MinimumFlipsCase &testCase = GetParam(); + const int result = minimumFlips(testCase.root, testCase.result); + EXPECT_EQ(result, testCase.expected); +} + +INSTANTIATE_TEST_SUITE_P( + MinimumFlipsTestCases, MinimumFlipsTest, + ::testing::Values( + MinimumFlipsCase{ + .test_name = "Leaf0ToTrue", .root = new TreeNode(0), .result = true, .expected = 1}, + MinimumFlipsCase{.test_name = "Leaf1AlreadyTrue", + .root = new TreeNode(1), + .result = true, + .expected = 0}, + MinimumFlipsCase{.test_name = "OrGateFlipChild", + .root = new TreeNode(2, new TreeNode(0), new TreeNode(0)), + .result = true, + .expected = 1}, + MinimumFlipsCase{.test_name = "OrGateToFalse", + .root = new TreeNode(2, new TreeNode(1), new TreeNode(1)), + .result = false, + .expected = 2}, + MinimumFlipsCase{.test_name = "AndGateAlreadyTrue", + .root = new TreeNode(3, new TreeNode(1), new TreeNode(1)), + .result = true, + .expected = 0}, + MinimumFlipsCase{.test_name = "AndGateFlipChild", + .root = new TreeNode(3, new TreeNode(1), new TreeNode(0)), + .result = true, + .expected = 1}, + MinimumFlipsCase{.test_name = "XorGateAlreadyTrue", + .root = new TreeNode(4, new TreeNode(1), new TreeNode(0)), + .result = true, + .expected = 0}, + MinimumFlipsCase{.test_name = "XorGateAlreadyFalse", + .root = new TreeNode(4, new TreeNode(1), new TreeNode(1)), + .result = false, + .expected = 0}, + MinimumFlipsCase{.test_name = "NotGateAlreadyTrue", + .root = new TreeNode(5, new TreeNode(0), nullptr), + .result = true, + .expected = 0}, + MinimumFlipsCase{.test_name = "NotGateAlreadyFalse", + .root = new TreeNode(5, new TreeNode(1), nullptr), + .result = false, + .expected = 0}, + MinimumFlipsCase{.test_name = "ComplexTreeOrAlreadyTrue", + .root = new TreeNode(2, new TreeNode(3, new TreeNode(1), new TreeNode(0)), + new TreeNode(5, new TreeNode(0), nullptr)), + .result = true, + .expected = 0}, + MinimumFlipsCase{.test_name = "ComplexTreeAndAlreadyFalse", + .root = new TreeNode(3, new TreeNode(2, new TreeNode(0), new TreeNode(0)), + new TreeNode(5, new TreeNode(1), nullptr)), + .result = false, + .expected = 0}, + MinimumFlipsCase{.test_name = "ComplexTreeXorNeedFlip", + .root = new TreeNode(4, new TreeNode(2, new TreeNode(1), new TreeNode(0)), + new TreeNode(3, new TreeNode(1), new TreeNode(1))), + .result = true, + .expected = 1}, + MinimumFlipsCase{.test_name = "ComplexTreeXorToFalse", + .root = new TreeNode(4, new TreeNode(2, new TreeNode(1), new TreeNode(1)), + new TreeNode(3, new TreeNode(1), new TreeNode(0))), + .result = false, + .expected = 1}, + MinimumFlipsCase{ + .test_name = "Leaf0ToFalse", .root = new TreeNode(0), .result = false, .expected = 0}, + MinimumFlipsCase{ + .test_name = "Leaf1ToFalse", .root = new TreeNode(1), .result = false, .expected = 1}, + MinimumFlipsCase{.test_name = "AndGateToFalse", + .root = new TreeNode(3, new TreeNode(0), new TreeNode(1)), + .result = false, + .expected = 0}, + MinimumFlipsCase{.test_name = "NotGateFlipToTrue", + .root = new TreeNode(5, new TreeNode(1), nullptr), + .result = true, + .expected = 1}, + MinimumFlipsCase{.test_name = "NotGateFlipToFalse", + .root = new TreeNode(5, new TreeNode(0), nullptr), + .result = false, + .expected = 1}), + [](const ::testing::TestParamInfo &info) { return info.param.test_name; }); \ No newline at end of file diff --git a/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.py b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.py new file mode 100644 index 0000000..0839b1a --- /dev/null +++ b/problems/minimum_flips_in_binary_tree_to_get_result/minimum_flips_in_binary_tree_to_get_result_test.py @@ -0,0 +1,84 @@ +"""Test cases for the minimumFlips function.""" + +import sys +from pathlib import Path + +import pytest + +# Add the root directory to Python path +root_dir = Path(__file__).parent.parent.parent +sys.path.insert(0, str(root_dir)) + +from common import TreeNode # noqa: E402 +from minimum_flips_in_binary_tree_to_get_result import minimumFlips # noqa: E402 + + +@pytest.mark.parametrize( + "root, result, expected", + [ + (TreeNode(0), True, 1), # Flip leaf node from 0 to 1 + (TreeNode(1), True, 0), # Leaf node already 1 + (TreeNode(2, TreeNode(0), TreeNode(0)), True, 1), # OR gate, flip one child + ( + TreeNode(2, TreeNode(1), TreeNode(1)), + False, + 2, + ), # OR gate, flip both children + (TreeNode(3, TreeNode(1), TreeNode(1)), True, 0), # AND gate, already true + (TreeNode(3, TreeNode(1), TreeNode(0)), True, 1), # AND gate, flip one child + (TreeNode(4, TreeNode(1), TreeNode(0)), True, 0), # XOR gate, already true + (TreeNode(4, TreeNode(1), TreeNode(1)), False, 0), # XOR gate, already false + (TreeNode(5, TreeNode(0)), True, 0), # NOT gate, already true + (TreeNode(5, TreeNode(1)), False, 0), # NOT gate, already false + ( + TreeNode( + 2, TreeNode(3, TreeNode(1), TreeNode(0)), TreeNode(5, TreeNode(0)) + ), + True, + 0, + ), # Complex tree: OR(AND(1,0), NOT(0)) = 1, already true + ( + TreeNode( + 3, TreeNode(2, TreeNode(0), TreeNode(0)), TreeNode(5, TreeNode(1)) + ), + False, + 0, + ), # Complex tree: AND(OR(0,0), NOT(1)) = 0, already false + ( + TreeNode( + 4, + TreeNode(2, TreeNode(1), TreeNode(0)), + TreeNode(3, TreeNode(1), TreeNode(1)), + ), + True, + 1, + ), # Complex tree: XOR(OR(1,0), AND(1,1)) = 1, need 1 flip to make false + ( + TreeNode( + 4, + TreeNode(2, TreeNode(1), TreeNode(1)), + TreeNode(3, TreeNode(1), TreeNode(0)), + ), + False, + 1, + ), # Complex tree: XOR(OR(1,1), AND(1,0)) = 1, flip one child to make it 0 + ], + ids=[ + "leaf_0_to_true", + "leaf_1_already_true", + "or_gate_flip_child", + "or_gate_to_false", + "and_gate_already_true", + "and_gate_flip_child", + "xor_gate_already_true", + "xor_gate_already_false", + "not_gate_already_true", + "not_gate_already_false", + "complex_tree_or_already_true", + "complex_tree_and_already_false", + "complex_tree_xor_need_flip", + "complex_tree_xor_to_false", + ], +) +def test_minimum_flips(root, result, expected): + assert minimumFlips(root, result) == expected