From 087bec7187ee27d48b837b9b5707dca601477293 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:18:53 +0000 Subject: [PATCH 1/3] Initial plan From 73647b49aadf7f86e9c06ee92dedbd0358518d29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:25:16 +0000 Subject: [PATCH 2/3] Add helpers folder with data structures, utilities, and demo notebooks Co-authored-by: jeremymanning <9030494+jeremymanning@users.noreply.github.com> --- helpers/README.md | 264 +++++++++++++++++++++ helpers/__init__.py | 68 ++++++ helpers/algorithms.py | 289 +++++++++++++++++++++++ helpers/data_structures.py | 32 +++ helpers/demo_binary_trees.ipynb | 331 ++++++++++++++++++++++++++ helpers/demo_linked_lists.ipynb | 400 ++++++++++++++++++++++++++++++++ helpers/linked_list_utils.py | 132 +++++++++++ helpers/tree_utils.py | 200 ++++++++++++++++ 8 files changed, 1716 insertions(+) create mode 100644 helpers/README.md create mode 100644 helpers/__init__.py create mode 100644 helpers/algorithms.py create mode 100644 helpers/data_structures.py create mode 100644 helpers/demo_binary_trees.ipynb create mode 100644 helpers/demo_linked_lists.ipynb create mode 100644 helpers/linked_list_utils.py create mode 100644 helpers/tree_utils.py diff --git a/helpers/README.md b/helpers/README.md new file mode 100644 index 0000000..1d9f2f9 --- /dev/null +++ b/helpers/README.md @@ -0,0 +1,264 @@ +# LeetCode Helper Functions + +This folder contains utility functions and data structures to help with debugging and solving LeetCode problems. + +## 📚 Contents + +- **[data_structures.py](data_structures.py)**: Common data structures (TreeNode, ListNode) +- **[tree_utils.py](tree_utils.py)**: Binary tree helper functions +- **[linked_list_utils.py](linked_list_utils.py)**: Linked list helper functions +- **[algorithms.py](algorithms.py)**: Common algorithms (BFS, DFS, tree search) + +## 🚀 Demo Notebooks + +Interactive Jupyter notebooks demonstrating the helper functions: + +- **[Binary Trees Demo](demo_binary_trees.ipynb)** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ContextLab/leetcode-solutions/blob/main/helpers/demo_binary_trees.ipynb) +- **[Linked Lists Demo](demo_linked_lists.ipynb)** [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ContextLab/leetcode-solutions/blob/main/helpers/demo_linked_lists.ipynb) + +## 📖 Quick Start + +### Installation + +From the root of the repository: + +```python +from helpers import * +``` + +### Binary Trees + +#### Creating Trees + +```python +from helpers import list_to_tree, print_tree + +# Create a tree from a list (LeetCode format) +tree = list_to_tree([1, 2, 3, 4, 5, None, 7]) + +# Visualize it +print_tree(tree) +# Output: +# Root: 1 +# ├─ L: 2 +# │ ├─ L: 4 +# │ └─ R: 5 +# └─ R: 3 +# └─ R: 7 +``` + +#### Converting Trees + +```python +from helpers import tree_to_list + +# Convert a tree back to a list +tree_list = tree_to_list(tree) +print(tree_list) # [1, 2, 3, 4, 5, None, 7] +``` + +#### Tree Traversals + +```python +from helpers import bfs_traversal, dfs_preorder, dfs_inorder, dfs_postorder + +tree = list_to_tree([1, 2, 3, 4, 5]) + +print(bfs_traversal(tree)) # [1, 2, 3, 4, 5] +print(dfs_preorder(tree)) # [1, 2, 4, 5, 3] +print(dfs_inorder(tree)) # [4, 2, 5, 1, 3] +print(dfs_postorder(tree)) # [4, 5, 2, 3, 1] +``` + +### Linked Lists + +#### Creating Linked Lists + +```python +from helpers import list_to_linked_list, print_linked_list + +# Create a linked list from a Python list +head = list_to_linked_list([1, 2, 3, 4, 5]) + +# Visualize it +print_linked_list(head) +# Output: 1 -> 2 -> 3 -> 4 -> 5 -> None +``` + +#### Converting Linked Lists + +```python +from helpers import linked_list_to_list + +# Convert back to a Python list +result = linked_list_to_list(head) +print(result) # [1, 2, 3, 4, 5] +``` + +#### Accessing Nodes + +```python +from helpers import get_node_at_index, get_linked_list_length + +head = list_to_linked_list([10, 20, 30, 40, 50]) + +# Get length +length = get_linked_list_length(head) # 5 + +# Get node at index +node = get_node_at_index(head, 2) +print(node.val) # 30 +``` + +## 📋 Complete Function Reference + +### Data Structures + +#### TreeNode +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None) +``` +Standard binary tree node used in LeetCode problems. + +#### ListNode +```python +class ListNode: + def __init__(self, val=0, next=None) +``` +Standard linked list node used in LeetCode problems. + +### Binary Tree Functions + +#### Conversion Functions +- `list_to_tree(values)`: Convert a list to a binary tree (level-order) +- `tree_to_list(root)`: Convert a binary tree to a list (level-order) + +#### Visualization Functions +- `print_tree(root)`: Print a visual representation of a tree +- `visualize_tree(root)`: Get a string representation of a tree + +#### Tree Properties +- `get_tree_height(root)`: Get the height of a tree +- `count_nodes(root)`: Count total nodes in a tree + +### Linked List Functions + +#### Conversion Functions +- `list_to_linked_list(values)`: Convert a Python list to a linked list +- `linked_list_to_list(head)`: Convert a linked list to a Python list + +#### Visualization Functions +- `print_linked_list(head)`: Print a visual representation of a linked list + +#### List Properties +- `get_linked_list_length(head)`: Get the length of a linked list +- `get_node_at_index(head, index)`: Get the node at a specific index + +### Algorithm Functions + +#### Tree Traversals +- `bfs_traversal(root)`: Breadth-first search (level-order) traversal +- `dfs_preorder(root)`: Depth-first search pre-order traversal +- `dfs_inorder(root)`: Depth-first search in-order traversal +- `dfs_postorder(root)`: Depth-first search post-order traversal +- `level_order_traversal(root)`: Level-order traversal grouped by level + +#### Tree Search +- `find_path_to_node(root, target)`: Find path from root to a target node +- `lowest_common_ancestor(root, p, q)`: Find the lowest common ancestor of two nodes +- `search_bst(root, target)`: Search for a value in a Binary Search Tree +- `is_valid_bst(root)`: Check if a tree is a valid Binary Search Tree + +## 💡 Usage Examples + +### Example 1: Testing Your Solution + +```python +from helpers import list_to_tree, tree_to_list + +def your_solution(root): + # Your solution code here + pass + +# Test with LeetCode test case +test_input = [1, 2, 3, 4, 5, None, 7] +tree = list_to_tree(test_input) +result = your_solution(tree) +print(tree_to_list(result)) +``` + +### Example 2: Debugging Tree Structure + +```python +from helpers import list_to_tree, print_tree, get_tree_height, count_nodes + +tree = list_to_tree([1, 2, 3, 4, 5, 6, 7, 8]) + +print("Tree structure:") +print_tree(tree) + +print(f"\nHeight: {get_tree_height(tree)}") +print(f"Total nodes: {count_nodes(tree)}") +``` + +### Example 3: Visualizing Linked List Operations + +```python +from helpers import list_to_linked_list, print_linked_list + +# Before operation +head = list_to_linked_list([1, 2, 3, 4, 5]) +print("Before:") +print_linked_list(head) + +# Your operation here (e.g., reverse) +# ... + +print("\nAfter:") +print_linked_list(head) +``` + +### Example 4: Understanding Tree Traversals + +```python +from helpers import list_to_tree, print_tree +from helpers import bfs_traversal, dfs_preorder, dfs_inorder, dfs_postorder + +tree = list_to_tree([1, 2, 3, 4, 5, 6, 7]) + +print("Tree:") +print_tree(tree) +print() + +print("Traversals:") +print(f"BFS: {bfs_traversal(tree)}") +print(f"Pre-order: {dfs_preorder(tree)}") +print(f"In-order: {dfs_inorder(tree)}") +print(f"Post-order: {dfs_postorder(tree)}") +``` + +## 🤝 Contributing + +Feel free to add more helper functions as you encounter common patterns in LeetCode problems! + +To add a new helper: +1. Add the function to the appropriate module (`tree_utils.py`, `linked_list_utils.py`, etc.) +2. Update `__init__.py` to export the function +3. Add documentation and examples +4. Update this README + +## 📝 Notes + +- All functions follow LeetCode's standard conventions for data structures +- `None` in tree lists represents null nodes (LeetCode format) +- Functions are designed to be copy-paste friendly for LeetCode submissions +- Visualization functions are great for debugging but won't work on LeetCode (they don't affect solutions) + +## 🔗 Related Resources + +- [LeetCode Official Site](https://leetcode.com) +- [Main Repository README](../README.md) +- [Problems We've Solved](../problems) + +Happy coding! 🎉 diff --git a/helpers/__init__.py b/helpers/__init__.py new file mode 100644 index 0000000..149351d --- /dev/null +++ b/helpers/__init__.py @@ -0,0 +1,68 @@ +""" +LeetCode Helper Functions + +This package provides utility functions and data structures for solving LeetCode problems. + +Main modules: +- data_structures: Common data structures (TreeNode, ListNode) +- tree_utils: Binary tree utilities (conversions, visualization) +- linked_list_utils: Linked list utilities (conversions, helpers) +- algorithms: Common algorithms (BFS, DFS, tree search) +""" + +from .data_structures import TreeNode, ListNode +from .tree_utils import ( + list_to_tree, + tree_to_list, + print_tree, + visualize_tree, + get_tree_height, + count_nodes +) +from .linked_list_utils import ( + list_to_linked_list, + linked_list_to_list, + print_linked_list, + get_linked_list_length, + get_node_at_index +) +from .algorithms import ( + bfs_traversal, + dfs_preorder, + dfs_inorder, + dfs_postorder, + level_order_traversal, + find_path_to_node, + lowest_common_ancestor, + search_bst, + is_valid_bst +) + +__all__ = [ + # Data structures + 'TreeNode', + 'ListNode', + # Tree utilities + 'list_to_tree', + 'tree_to_list', + 'print_tree', + 'visualize_tree', + 'get_tree_height', + 'count_nodes', + # Linked list utilities + 'list_to_linked_list', + 'linked_list_to_list', + 'print_linked_list', + 'get_linked_list_length', + 'get_node_at_index', + # Algorithms + 'bfs_traversal', + 'dfs_preorder', + 'dfs_inorder', + 'dfs_postorder', + 'level_order_traversal', + 'find_path_to_node', + 'lowest_common_ancestor', + 'search_bst', + 'is_valid_bst', +] diff --git a/helpers/algorithms.py b/helpers/algorithms.py new file mode 100644 index 0000000..914da13 --- /dev/null +++ b/helpers/algorithms.py @@ -0,0 +1,289 @@ +""" +Common algorithms for tree and graph traversal. +""" + +from typing import Optional, List, Callable +from collections import deque +from .data_structures import TreeNode + + +def bfs_traversal(root: Optional[TreeNode]) -> List[int]: + """ + Breadth-First Search (BFS) traversal of a binary tree. + Also known as level-order traversal. + + Args: + root: Root node of the binary tree + + Returns: + List of node values in BFS order + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> bfs_traversal(tree) + [1, 2, 3, 4, 5] + """ + if not root: + return [] + + result = [] + queue = deque([root]) + + while queue: + node = queue.popleft() + result.append(node.val) + + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + + return result + + +def dfs_preorder(root: Optional[TreeNode]) -> List[int]: + """ + Depth-First Search (DFS) traversal - Pre-order (Root, Left, Right). + + Args: + root: Root node of the binary tree + + Returns: + List of node values in pre-order + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> dfs_preorder(tree) + [1, 2, 4, 5, 3] + """ + if not root: + return [] + + result = [root.val] + result.extend(dfs_preorder(root.left)) + result.extend(dfs_preorder(root.right)) + + return result + + +def dfs_inorder(root: Optional[TreeNode]) -> List[int]: + """ + Depth-First Search (DFS) traversal - In-order (Left, Root, Right). + For Binary Search Trees, this gives sorted order. + + Args: + root: Root node of the binary tree + + Returns: + List of node values in in-order + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> dfs_inorder(tree) + [4, 2, 5, 1, 3] + """ + if not root: + return [] + + result = [] + result.extend(dfs_inorder(root.left)) + result.append(root.val) + result.extend(dfs_inorder(root.right)) + + return result + + +def dfs_postorder(root: Optional[TreeNode]) -> List[int]: + """ + Depth-First Search (DFS) traversal - Post-order (Left, Right, Root). + + Args: + root: Root node of the binary tree + + Returns: + List of node values in post-order + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> dfs_postorder(tree) + [4, 5, 2, 3, 1] + """ + if not root: + return [] + + result = [] + result.extend(dfs_postorder(root.left)) + result.extend(dfs_postorder(root.right)) + result.append(root.val) + + return result + + +def level_order_traversal(root: Optional[TreeNode]) -> List[List[int]]: + """ + Level-order traversal returning nodes grouped by level. + + Args: + root: Root node of the binary tree + + Returns: + List of lists, where each inner list contains values at that level + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> level_order_traversal(tree) + [[1], [2, 3], [4, 5]] + """ + if not root: + return [] + + result = [] + queue = deque([root]) + + while queue: + level_size = len(queue) + level_nodes = [] + + for _ in range(level_size): + node = queue.popleft() + level_nodes.append(node.val) + + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + + result.append(level_nodes) + + return result + + +def find_path_to_node(root: Optional[TreeNode], target: int) -> Optional[List[int]]: + """ + Find the path from root to a target node value. + + Args: + root: Root node of the binary tree + target: Value to search for + + Returns: + List of node values representing the path, or None if not found + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> find_path_to_node(tree, 5) + [1, 2, 5] + """ + if not root: + return None + + if root.val == target: + return [root.val] + + # Search in left subtree + left_path = find_path_to_node(root.left, target) + if left_path: + return [root.val] + left_path + + # Search in right subtree + right_path = find_path_to_node(root.right, target) + if right_path: + return [root.val] + right_path + + return None + + +def lowest_common_ancestor(root: Optional[TreeNode], p: int, q: int) -> Optional[TreeNode]: + """ + Find the lowest common ancestor (LCA) of two nodes in a binary tree. + + Args: + root: Root node of the binary tree + p: Value of first node + q: Value of second node + + Returns: + The LCA node, or None if either node is not found + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([3, 5, 1, 6, 2, 0, 8]) + >>> lca = lowest_common_ancestor(tree, 5, 1) + >>> lca.val + 3 + """ + if not root: + return None + + if root.val == p or root.val == q: + return root + + left = lowest_common_ancestor(root.left, p, q) + right = lowest_common_ancestor(root.right, p, q) + + if left and right: + return root + + return left if left else right + + +def search_bst(root: Optional[TreeNode], target: int) -> Optional[TreeNode]: + """ + Search for a value in a Binary Search Tree. + + Args: + root: Root node of the BST + target: Value to search for + + Returns: + Node with the target value, or None if not found + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([4, 2, 7, 1, 3]) + >>> node = search_bst(tree, 2) + >>> node.val + 2 + """ + if not root or root.val == target: + return root + + if target < root.val: + return search_bst(root.left, target) + else: + return search_bst(root.right, target) + + +def is_valid_bst(root: Optional[TreeNode], min_val: float = float('-inf'), + max_val: float = float('inf')) -> bool: + """ + Check if a binary tree is a valid Binary Search Tree. + + Args: + root: Root node of the binary tree + min_val: Minimum allowed value (used internally for recursion) + max_val: Maximum allowed value (used internally for recursion) + + Returns: + True if the tree is a valid BST, False otherwise + + Example: + >>> from helpers.tree_utils import list_to_tree + >>> tree = list_to_tree([2, 1, 3]) + >>> is_valid_bst(tree) + True + """ + if not root: + return True + + if root.val <= min_val or root.val >= max_val: + return False + + return (is_valid_bst(root.left, min_val, root.val) and + is_valid_bst(root.right, root.val, max_val)) diff --git a/helpers/data_structures.py b/helpers/data_structures.py new file mode 100644 index 0000000..86b494b --- /dev/null +++ b/helpers/data_structures.py @@ -0,0 +1,32 @@ +""" +Common data structures used in LeetCode problems. +""" + +from typing import Optional + + +class TreeNode: + """ + Definition for a binary tree node. + This is the standard TreeNode class used in LeetCode binary tree problems. + """ + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + def __repr__(self): + return f"TreeNode({self.val})" + + +class ListNode: + """ + Definition for singly-linked list node. + This is the standard ListNode class used in LeetCode linked list problems. + """ + def __init__(self, val=0, next=None): + self.val = val + self.next = next + + def __repr__(self): + return f"ListNode({self.val})" diff --git a/helpers/demo_binary_trees.ipynb b/helpers/demo_binary_trees.ipynb new file mode 100644 index 0000000..63afc9d --- /dev/null +++ b/helpers/demo_binary_trees.ipynb @@ -0,0 +1,331 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Binary Tree Helper Functions Demo\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ContextLab/leetcode-solutions/blob/main/helpers/demo_binary_trees.ipynb)\n", + "\n", + "This notebook demonstrates the helper functions for working with binary trees in LeetCode problems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, let's import the necessary functions from the helpers package:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# If running in Colab, clone the repository\n", + "try:\n", + " import google.colab\n", + " !git clone https://github.com/ContextLab/leetcode-solutions.git\n", + " import sys\n", + " sys.path.insert(0, '/content/leetcode-solutions')\n", + "except:\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import (\n", + " TreeNode,\n", + " list_to_tree,\n", + " tree_to_list,\n", + " print_tree,\n", + " get_tree_height,\n", + " count_nodes\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating Binary Trees\n", + "\n", + "### From a List\n", + "\n", + "The most common way to create a binary tree is from a list representation (level-order):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a tree from a list (LeetCode format)\n", + "tree = list_to_tree([1, 2, 3, 4, 5, None, 7])\n", + "\n", + "print(\"Tree structure:\")\n", + "print_tree(tree)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Manually\n", + "\n", + "You can also create trees manually:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Manual tree creation\n", + "root = TreeNode(10)\n", + "root.left = TreeNode(5)\n", + "root.right = TreeNode(15)\n", + "root.left.left = TreeNode(3)\n", + "root.left.right = TreeNode(7)\n", + "\n", + "print(\"Manually created tree:\")\n", + "print_tree(root)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Converting Trees to Lists\n", + "\n", + "You can convert a tree back to a list representation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Convert tree to list\n", + "tree = list_to_tree([1, 2, 3, 4, 5, None, 7])\n", + "tree_list = tree_to_list(tree)\n", + "\n", + "print(f\"Tree as list: {tree_list}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tree Properties\n", + "\n", + "Get useful information about your tree:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "tree = list_to_tree([1, 2, 3, 4, 5, 6, 7, 8])\n", + "\n", + "print(f\"Tree height: {get_tree_height(tree)}\")\n", + "print(f\"Number of nodes: {count_nodes(tree)}\")\n", + "print(\"\\nTree structure:\")\n", + "print_tree(tree)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tree Traversals\n", + "\n", + "Now let's explore different tree traversal algorithms:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import (\n", + " bfs_traversal,\n", + " dfs_preorder,\n", + " dfs_inorder,\n", + " dfs_postorder,\n", + " level_order_traversal\n", + ")\n", + "\n", + "tree = list_to_tree([1, 2, 3, 4, 5, 6, 7])\n", + "\n", + "print(\"Tree:\")\n", + "print_tree(tree)\n", + "print()\n", + "\n", + "print(f\"BFS (Level-order): {bfs_traversal(tree)}\")\n", + "print(f\"DFS Pre-order: {dfs_preorder(tree)}\")\n", + "print(f\"DFS In-order: {dfs_inorder(tree)}\")\n", + "print(f\"DFS Post-order: {dfs_postorder(tree)}\")\n", + "print(f\"\\nLevel-order (grouped): {level_order_traversal(tree)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Finding Paths and Ancestors\n", + "\n", + "Use the search algorithms to find paths and common ancestors:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import find_path_to_node, lowest_common_ancestor\n", + "\n", + "tree = list_to_tree([3, 5, 1, 6, 2, 0, 8, None, None, 7, 4])\n", + "\n", + "print(\"Tree:\")\n", + "print_tree(tree)\n", + "print()\n", + "\n", + "# Find path to a node\n", + "target = 7\n", + "path = find_path_to_node(tree, target)\n", + "print(f\"Path to {target}: {path}\")\n", + "\n", + "# Find lowest common ancestor\n", + "p, q = 5, 1\n", + "lca = lowest_common_ancestor(tree, p, q)\n", + "print(f\"LCA of {p} and {q}: {lca.val if lca else None}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Binary Search Trees\n", + "\n", + "Work with Binary Search Trees:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import search_bst, is_valid_bst\n", + "\n", + "# Create a BST\n", + "bst = list_to_tree([4, 2, 7, 1, 3, 6, 9])\n", + "\n", + "print(\"BST:\")\n", + "print_tree(bst)\n", + "print()\n", + "\n", + "print(f\"Is valid BST? {is_valid_bst(bst)}\")\n", + "\n", + "# Search for a value\n", + "target = 2\n", + "result = search_bst(bst, target)\n", + "if result:\n", + " print(f\"\\nFound {target} in the BST\")\n", + " print(\"Subtree rooted at found node:\")\n", + " print_tree(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Practice Examples\n", + "\n", + "Here are some examples you might encounter in LeetCode problems:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example 1: Create a tree from LeetCode test case\n", + "test_case = [5, 4, 8, 11, None, 13, 4, 7, 2, None, None, None, 1]\n", + "tree = list_to_tree(test_case)\n", + "\n", + "print(\"Example tree from LeetCode:\")\n", + "print_tree(tree)\n", + "print(f\"\\nHeight: {get_tree_height(tree)}\")\n", + "print(f\"Total nodes: {count_nodes(tree)}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example 2: Work with empty and single-node trees\n", + "empty_tree = list_to_tree([])\n", + "single_node = list_to_tree([42])\n", + "\n", + "print(\"Empty tree:\")\n", + "print_tree(empty_tree)\n", + "print()\n", + "\n", + "print(\"Single node tree:\")\n", + "print_tree(single_node)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next Steps\n", + "\n", + "- Check out the [linked list demo notebook](demo_linked_lists.ipynb) for linked list helpers\n", + "- See the [helpers README](README.md) for complete documentation\n", + "- Browse the [problems folder](../problems) to see these helpers in action!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/helpers/demo_linked_lists.ipynb b/helpers/demo_linked_lists.ipynb new file mode 100644 index 0000000..8da0d9c --- /dev/null +++ b/helpers/demo_linked_lists.ipynb @@ -0,0 +1,400 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Linked List Helper Functions Demo\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ContextLab/leetcode-solutions/blob/main/helpers/demo_linked_lists.ipynb)\n", + "\n", + "This notebook demonstrates the helper functions for working with linked lists in LeetCode problems." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, let's import the necessary functions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# If running in Colab, clone the repository\n", + "try:\n", + " import google.colab\n", + " !git clone https://github.com/ContextLab/leetcode-solutions.git\n", + " import sys\n", + " sys.path.insert(0, '/content/leetcode-solutions')\n", + "except:\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from helpers import (\n", + " ListNode,\n", + " list_to_linked_list,\n", + " linked_list_to_list,\n", + " print_linked_list,\n", + " get_linked_list_length,\n", + " get_node_at_index\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating Linked Lists\n", + "\n", + "### From a List\n", + "\n", + "The easiest way to create a linked list is from a Python list:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a linked list from a Python list\n", + "head = list_to_linked_list([1, 2, 3, 4, 5])\n", + "\n", + "print(\"Linked list:\")\n", + "print_linked_list(head)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Manually\n", + "\n", + "You can also create linked lists manually:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Manual linked list creation\n", + "head = ListNode(10)\n", + "head.next = ListNode(20)\n", + "head.next.next = ListNode(30)\n", + "head.next.next.next = ListNode(40)\n", + "\n", + "print(\"Manually created linked list:\")\n", + "print_linked_list(head)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Converting Linked Lists to Python Lists\n", + "\n", + "Convert a linked list back to a Python list for easy inspection:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "head = list_to_linked_list([5, 10, 15, 20, 25])\n", + "result = linked_list_to_list(head)\n", + "\n", + "print(f\"Linked list as Python list: {result}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Linked List Properties\n", + "\n", + "Get useful information about your linked list:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "head = list_to_linked_list([1, 2, 3, 4, 5, 6, 7, 8])\n", + "\n", + "print(\"Linked list:\")\n", + "print_linked_list(head)\n", + "print(f\"\\nLength: {get_linked_list_length(head)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Accessing Nodes by Index\n", + "\n", + "Access specific nodes in the linked list:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "head = list_to_linked_list([10, 20, 30, 40, 50])\n", + "\n", + "print(\"Linked list:\")\n", + "print_linked_list(head)\n", + "print()\n", + "\n", + "# Access nodes by index\n", + "for i in range(5):\n", + " node = get_node_at_index(head, i)\n", + " if node:\n", + " print(f\"Node at index {i}: {node.val}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Common Linked List Operations\n", + "\n", + "Here are some common operations you might need:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example 1: Reverse a linked list\n", + "def reverse_linked_list(head):\n", + " prev = None\n", + " current = head\n", + " \n", + " while current:\n", + " next_node = current.next\n", + " current.next = prev\n", + " prev = current\n", + " current = next_node\n", + " \n", + " return prev\n", + "\n", + "head = list_to_linked_list([1, 2, 3, 4, 5])\n", + "print(\"Original:\")\n", + "print_linked_list(head)\n", + "\n", + "reversed_head = reverse_linked_list(head)\n", + "print(\"\\nReversed:\")\n", + "print_linked_list(reversed_head)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example 2: Find the middle node\n", + "def find_middle(head):\n", + " slow = fast = head\n", + " \n", + " while fast and fast.next:\n", + " slow = slow.next\n", + " fast = fast.next.next\n", + " \n", + " return slow\n", + "\n", + "head = list_to_linked_list([1, 2, 3, 4, 5])\n", + "print(\"Linked list:\")\n", + "print_linked_list(head)\n", + "\n", + "middle = find_middle(head)\n", + "print(f\"\\nMiddle node value: {middle.val}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example 3: Detect a cycle (creating a cycle manually for demo)\n", + "def has_cycle(head):\n", + " slow = fast = head\n", + " \n", + " while fast and fast.next:\n", + " slow = slow.next\n", + " fast = fast.next.next\n", + " \n", + " if slow == fast:\n", + " return True\n", + " \n", + " return False\n", + "\n", + "# Create a list without cycle\n", + "head1 = list_to_linked_list([1, 2, 3, 4, 5])\n", + "print(f\"List without cycle has cycle? {has_cycle(head1)}\")\n", + "\n", + "# Create a list with cycle (3 -> 4 -> 5 -> back to 3)\n", + "head2 = list_to_linked_list([1, 2, 3, 4, 5])\n", + "node3 = get_node_at_index(head2, 2) # Node with value 3\n", + "node5 = get_node_at_index(head2, 4) # Node with value 5\n", + "node5.next = node3 # Create cycle\n", + "\n", + "print(f\"List with cycle has cycle? {has_cycle(head2)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Merging Linked Lists\n", + "\n", + "Merge two sorted linked lists:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def merge_two_lists(l1, l2):\n", + " dummy = ListNode(0)\n", + " current = dummy\n", + " \n", + " while l1 and l2:\n", + " if l1.val < l2.val:\n", + " current.next = l1\n", + " l1 = l1.next\n", + " else:\n", + " current.next = l2\n", + " l2 = l2.next\n", + " current = current.next\n", + " \n", + " current.next = l1 if l1 else l2\n", + " \n", + " return dummy.next\n", + "\n", + "l1 = list_to_linked_list([1, 3, 5, 7])\n", + "l2 = list_to_linked_list([2, 4, 6, 8])\n", + "\n", + "print(\"List 1:\")\n", + "print_linked_list(l1)\n", + "print(\"\\nList 2:\")\n", + "print_linked_list(l2)\n", + "\n", + "merged = merge_two_lists(l1, l2)\n", + "print(\"\\nMerged:\")\n", + "print_linked_list(merged)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Practice Examples\n", + "\n", + "Here are some examples you might encounter in LeetCode problems:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example: Remove duplicates from sorted list\n", + "def remove_duplicates(head):\n", + " current = head\n", + " \n", + " while current and current.next:\n", + " if current.val == current.next.val:\n", + " current.next = current.next.next\n", + " else:\n", + " current = current.next\n", + " \n", + " return head\n", + "\n", + "head = list_to_linked_list([1, 1, 2, 3, 3, 4, 5, 5])\n", + "print(\"Original with duplicates:\")\n", + "print_linked_list(head)\n", + "\n", + "head = remove_duplicates(head)\n", + "print(\"\\nAfter removing duplicates:\")\n", + "print_linked_list(head)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example: Work with empty and single-node lists\n", + "empty_list = list_to_linked_list([])\n", + "single_node = list_to_linked_list([42])\n", + "\n", + "print(\"Empty list:\")\n", + "print_linked_list(empty_list)\n", + "print()\n", + "\n", + "print(\"Single node list:\")\n", + "print_linked_list(single_node)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Next Steps\n", + "\n", + "- Check out the [binary tree demo notebook](demo_binary_trees.ipynb) for tree helpers\n", + "- See the [helpers README](README.md) for complete documentation\n", + "- Browse the [problems folder](../problems) to see these helpers in action!" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/helpers/linked_list_utils.py b/helpers/linked_list_utils.py new file mode 100644 index 0000000..7d67b6c --- /dev/null +++ b/helpers/linked_list_utils.py @@ -0,0 +1,132 @@ +""" +Utility functions for working with linked lists. +""" + +from typing import Optional, List +from .data_structures import ListNode + + +def list_to_linked_list(values: List[int]) -> Optional[ListNode]: + """ + Convert a Python list to a singly-linked list. + + Args: + values: List of values to convert + + Returns: + Head node of the created linked list, or None if input is empty + + Example: + >>> head = list_to_linked_list([1, 2, 3, 4, 5]) + >>> # Creates: 1 -> 2 -> 3 -> 4 -> 5 + """ + if not values: + return None + + head = ListNode(values[0]) + current = head + + for val in values[1:]: + current.next = ListNode(val) + current = current.next + + return head + + +def linked_list_to_list(head: Optional[ListNode]) -> List[int]: + """ + Convert a singly-linked list to a Python list. + + Args: + head: Head node of the linked list + + Returns: + List of values from the linked list + + Example: + >>> head = ListNode(1) + >>> head.next = ListNode(2) + >>> head.next.next = ListNode(3) + >>> linked_list_to_list(head) + [1, 2, 3] + """ + result = [] + current = head + + while current: + result.append(current.val) + current = current.next + + return result + + +def print_linked_list(head: Optional[ListNode]) -> None: + """ + Print a visual representation of a linked list. + + Args: + head: Head node of the linked list + + Example: + >>> head = list_to_linked_list([1, 2, 3, 4]) + >>> print_linked_list(head) + 1 -> 2 -> 3 -> 4 -> None + """ + if not head: + print("Empty list") + return + + values = linked_list_to_list(head) + print(" -> ".join(map(str, values)) + " -> None") + + +def get_linked_list_length(head: Optional[ListNode]) -> int: + """ + Calculate the length of a linked list. + + Args: + head: Head node of the linked list + + Returns: + Number of nodes in the linked list + + Example: + >>> head = list_to_linked_list([1, 2, 3, 4, 5]) + >>> get_linked_list_length(head) + 5 + """ + length = 0 + current = head + + while current: + length += 1 + current = current.next + + return length + + +def get_node_at_index(head: Optional[ListNode], index: int) -> Optional[ListNode]: + """ + Get the node at a specific index in the linked list. + + Args: + head: Head node of the linked list + index: Zero-based index of the node to retrieve + + Returns: + Node at the specified index, or None if index is out of bounds + + Example: + >>> head = list_to_linked_list([1, 2, 3, 4, 5]) + >>> node = get_node_at_index(head, 2) + >>> node.val + 3 + """ + current = head + current_index = 0 + + while current and current_index < index: + current = current.next + current_index += 1 + + return current diff --git a/helpers/tree_utils.py b/helpers/tree_utils.py new file mode 100644 index 0000000..de33ac7 --- /dev/null +++ b/helpers/tree_utils.py @@ -0,0 +1,200 @@ +""" +Utility functions for working with binary trees. +""" + +from typing import Optional, List +from collections import deque +from .data_structures import TreeNode + + +def list_to_tree(values: List[Optional[int]]) -> Optional[TreeNode]: + """ + Convert a list to a binary tree (level-order representation). + + This matches LeetCode's format where None represents null nodes. + + Args: + values: List of values in level-order, with None for null nodes + + Returns: + Root node of the created binary tree, or None if input is empty + + Example: + >>> tree = list_to_tree([1, 2, 3, 4, 5, None, 7]) + >>> # Creates: + >>> 1 + >>> / \\ + >>> 2 3 + >>> / \\ \\ + >>> 4 5 7 + """ + if not values or values[0] is None: + return None + + root = TreeNode(values[0]) + queue = deque([root]) + i = 1 + + while queue and i < len(values): + node = queue.popleft() + + # Process left child + if i < len(values) and values[i] is not None: + node.left = TreeNode(values[i]) + queue.append(node.left) + i += 1 + + # Process right child + if i < len(values) and values[i] is not None: + node.right = TreeNode(values[i]) + queue.append(node.right) + i += 1 + + return root + + +def tree_to_list(root: Optional[TreeNode]) -> List[Optional[int]]: + """ + Convert a binary tree to a list (level-order representation). + + This matches LeetCode's format where None represents null nodes. + Trailing None values are removed. + + Args: + root: Root node of the binary tree + + Returns: + List of values in level-order, with None for null nodes + + Example: + >>> tree = TreeNode(1) + >>> tree.left = TreeNode(2) + >>> tree.right = TreeNode(3) + >>> tree_to_list(tree) + [1, 2, 3] + """ + if not root: + return [] + + result = [] + queue = deque([root]) + + while queue: + node = queue.popleft() + + if node is None: + result.append(None) + else: + result.append(node.val) + queue.append(node.left) + queue.append(node.right) + + # Remove trailing None values + while result and result[-1] is None: + result.pop() + + return result + + +def visualize_tree(root: Optional[TreeNode], level: int = 0, prefix: str = "Root: ") -> str: + """ + Create a visual string representation of a binary tree. + + Args: + root: Root node of the binary tree + level: Current depth level (used for recursion, default 0) + prefix: Prefix string for the current node (default "Root: ") + + Returns: + String representation of the tree structure + + Example: + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> print(visualize_tree(tree)) + Root: 1 + ├─ L: 2 + │ ├─ L: 4 + │ └─ R: 5 + └─ R: 3 + """ + if not root: + return "" + + lines = [] + lines.append(prefix + str(root.val)) + + if root.left or root.right: + if root.left: + extension = "│ " if root.right else " " + lines.append(visualize_tree(root.left, level + 1, + ("│ " * level) + "├─ L: ").rstrip()) + + if root.right: + lines.append(visualize_tree(root.right, level + 1, + ("│ " * level) + "└─ R: ").rstrip()) + + return "\n".join(lines) + + +def print_tree(root: Optional[TreeNode]) -> None: + """ + Print a visual representation of a binary tree. + + Args: + root: Root node of the binary tree + + Example: + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> print_tree(tree) + Root: 1 + ├─ L: 2 + │ ├─ L: 4 + │ └─ R: 5 + └─ R: 3 + """ + if not root: + print("Empty tree") + else: + print(visualize_tree(root)) + + +def get_tree_height(root: Optional[TreeNode]) -> int: + """ + Calculate the height of a binary tree. + + Args: + root: Root node of the binary tree + + Returns: + Height of the tree (number of edges on longest path from root to leaf) + + Example: + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> get_tree_height(tree) + 2 + """ + if not root: + return -1 + + return 1 + max(get_tree_height(root.left), get_tree_height(root.right)) + + +def count_nodes(root: Optional[TreeNode]) -> int: + """ + Count the total number of nodes in a binary tree. + + Args: + root: Root node of the binary tree + + Returns: + Total number of nodes in the tree + + Example: + >>> tree = list_to_tree([1, 2, 3, 4, 5]) + >>> count_nodes(tree) + 5 + """ + if not root: + return 0 + + return 1 + count_nodes(root.left) + count_nodes(root.right) From ab8cc890f44bcc421b7e0aca2d7e80f644303e1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 20:26:52 +0000 Subject: [PATCH 3/3] Fix tree visualization and test all helper functions Co-authored-by: jeremymanning <9030494+jeremymanning@users.noreply.github.com> --- helpers/tree_utils.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/helpers/tree_utils.py b/helpers/tree_utils.py index de33ac7..965b538 100644 --- a/helpers/tree_utils.py +++ b/helpers/tree_utils.py @@ -96,14 +96,15 @@ def tree_to_list(root: Optional[TreeNode]) -> List[Optional[int]]: return result -def visualize_tree(root: Optional[TreeNode], level: int = 0, prefix: str = "Root: ") -> str: +def visualize_tree(root: Optional[TreeNode], prefix: str = "", is_tail: bool = True, is_root: bool = True) -> str: """ Create a visual string representation of a binary tree. Args: root: Root node of the binary tree - level: Current depth level (used for recursion, default 0) - prefix: Prefix string for the current node (default "Root: ") + prefix: Prefix string for the current node (used internally) + is_tail: Whether this is the last child (used internally) + is_root: Whether this is the root node (default True) Returns: String representation of the tree structure @@ -121,17 +122,34 @@ def visualize_tree(root: Optional[TreeNode], level: int = 0, prefix: str = "Root return "" lines = [] - lines.append(prefix + str(root.val)) - if root.left or root.right: - if root.left: - extension = "│ " if root.right else " " - lines.append(visualize_tree(root.left, level + 1, - ("│ " * level) + "├─ L: ").rstrip()) + # Root node or child node label + if is_root: + lines.append("Root: " + str(root.val)) + new_prefix = "" + else: + lines.append(prefix + str(root.val)) + new_prefix = prefix + (" " if is_tail else "│ ") + + # Process children + children = [] + if root.left: + children.append(('L', root.left)) + if root.right: + children.append(('R', root.right)) + + for i, (label, child) in enumerate(children): + is_last = (i == len(children) - 1) + connector = "└─ " if is_last else "├─ " + child_lines = visualize_tree(child, new_prefix, is_last, False) - if root.right: - lines.append(visualize_tree(root.right, level + 1, - ("│ " * level) + "└─ R: ").rstrip()) + if child_lines: + # Add the label to the first line + first_line = new_prefix + connector + label + ": " + str(child.val) + remaining_lines = child_lines.split('\n')[1:] if '\n' in child_lines else [] + + lines.append(first_line) + lines.extend(remaining_lines) return "\n".join(lines)