Skip to content

Instantly share code, notes, and snippets.

@desujoy
Last active June 27, 2025 05:44
Show Gist options
  • Save desujoy/e0d32e17a095b86ab063d8f4996a3eeb to your computer and use it in GitHub Desktop.
Save desujoy/e0d32e17a095b86ab063d8f4996a3eeb to your computer and use it in GitHub Desktop.

🟑 Tier 2 – Problem-Solving Core

1. Sorting

Concepts:

  • Built-in sorting algorithms (Timsort in Python)
  • Custom sorting with key functions and comparators
  • Stable vs unstable sorting, in-place vs out-of-place
  • Sorting as preprocessing step for other algorithms

Essential Sorting Templates:

# Basic sorting operations
arr = [3, 1, 4, 1, 5, 9, 2, 6]

# In-place sorting
arr.sort()                          # Ascending: [1, 1, 2, 3, 4, 5, 6, 9]
arr.sort(reverse=True)              # Descending: [9, 6, 5, 4, 3, 2, 1, 1]

# Return new sorted list
sorted_arr = sorted(arr)            # Original unchanged
sorted_desc = sorted(arr, reverse=True)

# Custom key functions
words = ["apple", "pie", "cherry", "a"]
words.sort(key=len)                 # Sort by length: ['a', 'pie', 'apple', 'cherry']
words.sort(key=str.lower)           # Case-insensitive sort

# Sort by multiple criteria
students = [("Alice", 85, 20), ("Bob", 90, 19), ("Charlie", 85, 21)]
# Sort by grade (desc), then by age (asc)
students.sort(key=lambda x: (-x[1], x[2]))

# Sort tuples/lists by specific positions
points = [(1, 3), (2, 1), (1, 1), (2, 3)]
points.sort()                       # Sort by first element, then second: [(1, 1), (1, 3), (2, 1), (2, 3)]
points.sort(key=lambda x: x[1])     # Sort by second element: [(2, 1), (1, 1), (1, 3), (2, 3)]

# Sort strings by custom criteria
def sort_by_vowels_then_length(word):
    vowel_count = sum(1 for c in word.lower() if c in 'aeiou')
    return (vowel_count, len(word))

words.sort(key=sort_by_vowels_then_length)

Advanced Sorting Patterns:

# Merge intervals (requires sorting)
def merge_intervals(intervals):
    """Merge overlapping intervals"""
    if not intervals:
        return []
    
    # Sort by start time
    intervals.sort(key=lambda x: x[0])
    merged = [intervals[0]]
    
    for current in intervals[1:]:
        last = merged[-1]
        if current[0] <= last[1]:  # Overlapping
            merged[-1] = [last[0], max(last[1], current[1])]
        else:
            merged.append(current)
    
    return merged

# Meeting rooms problem
def can_attend_meetings(intervals):
    """Check if person can attend all meetings"""
    intervals.sort(key=lambda x: x[0])
    
    for i in range(1, len(intervals)):
        if intervals[i][0] < intervals[i-1][1]:
            return False
    
    return True

# Minimum meeting rooms required
def min_meeting_rooms(intervals):
    """Find minimum number of meeting rooms required"""
    if not intervals:
        return 0
    
    start_times = sorted([i[0] for i in intervals])
    end_times = sorted([i[1] for i in intervals])
    
    rooms = 0
    end_ptr = 0
    
    for start in start_times:
        if start >= end_times[end_ptr]:
            end_ptr += 1
        else:
            rooms += 1
    
    return rooms

# Custom comparator for complex objects
from functools import cmp_to_key

def compare_strings(a, b):
    """Custom comparator: shorter strings first, then lexicographic"""
    if len(a) != len(b):
        return len(a) - len(b)
    if a < b:
        return -1
    elif a > b:
        return 1
    else:
        return 0

words = ["apple", "pie", "a", "cherry"]
words.sort(key=cmp_to_key(compare_strings))

# Largest number from array
def largest_number(nums):
    """Arrange numbers to form largest possible number"""
    def compare(a, b):
        if a + b > b + a:
            return -1
        elif a + b < b + a:
            return 1
        else:
            return 0
    
    nums_str = list(map(str, nums))
    nums_str.sort(key=cmp_to_key(compare))
    
    result = ''.join(nums_str)
    return '0' if result[0] == '0' else result

# Sort by frequency
def sort_by_frequency(arr):
    """Sort array by frequency of elements"""
    from collections import Counter
    
    freq = Counter(arr)
    # Sort by frequency (desc), then by value (asc)
    return sorted(arr, key=lambda x: (-freq[x], x))

# Top K frequent elements
def top_k_frequent(nums, k):
    """Find k most frequent elements"""
    from collections import Counter
    import heapq
    
    freq = Counter(nums)
    return heapq.nlargest(k, freq.keys(), key=freq.get)

Sorting Algorithm Implementations:

# Quick Sort
def quick_sort(arr, low=0, high=None):
    """Quick sort implementation"""
    if high is None:
        high = len(arr) - 1
    
    if low < high:
        pivot_idx = partition(arr, low, high)
        quick_sort(arr, low, pivot_idx - 1)
        quick_sort(arr, pivot_idx + 1, high)

def partition(arr, low, high):
    """Partition function for quick sort"""
    pivot = arr[high]
    i = low - 1
    
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

# Merge Sort
def merge_sort(arr):
    """Merge sort implementation"""
    if len(arr) <= 1:
        return arr
    
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    
    return merge(left, right)

def merge(left, right):
    """Merge two sorted arrays"""
    result = []
    i = j = 0
    
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    
    result.extend(left[i:])
    result.extend(right[j:])
    return result

# Counting Sort (for bounded integers)
def counting_sort(arr, max_val):
    """Counting sort for integers in range [0, max_val]"""
    count = [0] * (max_val + 1)
    
    # Count frequencies
    for num in arr:
        count[num] += 1
    
    # Reconstruct sorted array
    result = []
    for val, freq in enumerate(count):
        result.extend([val] * freq)
    
    return result

# Bucket Sort
def bucket_sort(arr):
    """Bucket sort for floating point numbers in [0, 1)"""
    n = len(arr)
    buckets = [[] for _ in range(n)]
    
    # Distribute elements into buckets
    for num in arr:
        bucket_idx = int(num * n)
        buckets[bucket_idx].append(num)
    
    # Sort individual buckets and concatenate
    result = []
    for bucket in buckets:
        bucket.sort()
        result.extend(bucket)
    
    return result

Sorting-Based Problem Patterns:

# 3Sum problem (requires sorting)
def three_sum(nums):
    """Find all unique triplets that sum to zero"""
    nums.sort()
    result = []
    
    for i in range(len(nums) - 2):
        if i > 0 and nums[i] == nums[i-1]:
            continue  # Skip duplicates
        
        left, right = i + 1, len(nums) - 1
        target = -nums[i]
        
        while left < right:
            current_sum = nums[left] + nums[right]
            if current_sum == target:
                result.append([nums[i], nums[left], nums[right]])
                
                # Skip duplicates
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1
                
                left += 1
                right -= 1
            elif current_sum < target:
                left += 1
            else:
                right -= 1
    
    return result

# Sort colors (Dutch National Flag)
def sort_colors(nums):
    """Sort array of 0s, 1s, and 2s in-place"""
    low = mid = 0
    high = len(nums) - 1
    
    while mid <= high:
        if nums[mid] == 0:
            nums[low], nums[mid] = nums[mid], nums[low]
            low += 1
            mid += 1
        elif nums[mid] == 1:
            mid += 1
        else:  # nums[mid] == 2
            nums[mid], nums[high] = nums[high], nums[mid]
            high -= 1
            # Don't increment mid as we need to check swapped element

# Find kth largest element
def find_kth_largest(nums, k):
    """Find kth largest element using sorting"""
    nums.sort(reverse=True)
    return nums[k-1]

# Alternative: Using quickselect (more efficient)
def quickselect_kth_largest(nums, k):
    """Find kth largest using quickselect algorithm"""
    import random
    
    def quickselect(arr, k):
        if len(arr) == 1:
            return arr[0]
        
        pivot = random.choice(arr)
        smaller = [x for x in arr if x < pivot]
        equal = [x for x in arr if x == pivot]
        larger = [x for x in arr if x > pivot]
        
        if k <= len(larger):
            return quickselect(larger, k)
        elif k <= len(larger) + len(equal):
            return pivot
        else:
            return quickselect(smaller, k - len(larger) - len(equal))
    
    return quickselect(nums, k)

# Wiggle sort
def wiggle_sort(nums):
    """Reorder array so nums[0] < nums[1] > nums[2] < nums[3]..."""
    nums.sort()
    n = len(nums)
    temp = nums[:]
    
    # Fill odd indices with larger half
    # Fill even indices with smaller half
    left, right = (n - 1) // 2, n - 1
    
    for i in range(n):
        if i % 2 == 0:
            nums[i] = temp[left]
            left -= 1
        else:
            nums[i] = temp[right]
            right -= 1

Pro Tips:

  • Python's sort() is stable and uses Timsort (hybrid of merge sort and insertion sort)
  • Use key parameter instead of cmp for custom sorting (more efficient)
  • Sort first when dealing with intervals, pairs, or triplets problems
  • Consider counting sort for bounded integer ranges
  • Sorting often reduces problem complexity from O(nΒ²) to O(n log n)
  • Learn to identify when sorting is beneficial as preprocessing step

2. Binary Search

Concepts:

  • Efficient O(log n) search in sorted arrays
  • Lower bound, upper bound, and condition-based binary search
  • Works on any monotonic function, not just arrays
  • Template method for optimization problems

Essential Binary Search Templates:

# Standard Binary Search (exact match)
def binary_search(arr, target):
    """Find index of target in sorted array, return -1 if not found"""
    left, right = 0, len(arr) - 1
    
    while left <= right:
        mid = left + (right - left) // 2  # Avoid overflow
        
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    
    return -1

# Lower Bound (first position >= target)
def lower_bound(arr, target):
    """Find first index where arr[i] >= target"""
    left, right = 0, len(arr)
    
    while left < right:
        mid = left + (right - left) // 2
        
        if arr[mid] < target:
            left = mid + 1
        else:
            right = mid
    
    return left

# Upper Bound (first position > target)
def upper_bound(arr, target):
    """Find first index where arr[i] > target"""
    left, right = 0, len(arr)
    
    while left < right:
        mid = left + (right - left) // 2
        
        if arr[mid] <= target:
            left = mid + 1
        else:
            right = mid
    
    return left

# Find range of target (first and last occurrence)
def search_range(arr, target):
    """Find first and last position of target"""
    def find_first():
        left, right = 0, len(arr) - 1
        first = -1
        
        while left <= right:
            mid = left + (right - left) // 2
            
            if arr[mid] == target:
                first = mid
                right = mid - 1  # Continue searching left
            elif arr[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        
        return first
    
    def find_last():
        left, right = 0, len(arr) - 1
        last = -1
        
        while left <= right:
            mid = left + (right - left) // 2
            
            if arr[mid] == target:
                last = mid
                left = mid + 1  # Continue searching right
            elif arr[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        
        return last
    
    first = find_first()
    if first == -1:
        return [-1, -1]
    
    last = find_last()
    return [first, last]

Advanced Binary Search Patterns:

# Search in Rotated Sorted Array
def search_rotated_array(nums, target):
    """Find target in rotated sorted array"""
    left, right = 0, len(nums) - 1
    
    while left <= right:
        mid = left + (right - left) // 2
        
        if nums[mid] == target:
            return mid
        
        # Check which half is sorted
        if nums[left] <= nums[mid]:  # Left half is sorted
            if nums[left] <= target < nums[mid]:
                right = mid - 1
            else:
                left = mid + 1
        else:  # Right half is sorted
            if nums[mid] < target <= nums[right]:
                left = mid + 1
            else:
                right = mid - 1
    
    return -1

# Find Minimum in Rotated Sorted Array
def find_min_rotated(nums):
    """Find minimum element in rotated sorted array"""
    left, right = 0, len(nums) - 1
    
    while left < right:
        mid = left + (right - left) // 2
        
        if nums[mid] > nums[right]:
            # Minimum is in right half
            left = mid + 1
        else:
            # Minimum is in left half (including mid)
            right = mid
    
    return nums[left]

# Find Peak Element
def find_peak_element(nums):
    """Find any peak element (nums[i] > nums[i-1] and nums[i] > nums[i+1])"""
    left, right = 0, len(nums) - 1
    
    while left < right:
        mid = left + (right - left) // 2
        
        if nums[mid] > nums[mid + 1]:
            # Peak is in left half (including mid)
            right = mid
        else:
            # Peak is in right half
            left = mid + 1
    
    return left

# Search in 2D Matrix
def search_2d_matrix(matrix, target):
    """Search target in 2D matrix where each row and column is sorted"""
    if not matrix or not matrix[0]:
        return False
    
    rows, cols = len(matrix), len(matrix[0])
    left, right = 0, rows * cols - 1
    
    while left <= right:
        mid = left + (right - left) // 2
        mid_val = matrix[mid // cols][mid % cols]
        
        if mid_val == target:
            return True
        elif mid_val < target:
            left = mid + 1
        else:
            right = mid - 1
    
    return False

# Alternative: Search 2D Matrix II (different constraint)
def search_2d_matrix_ii(matrix, target):
    """Search in matrix where rows and columns are sorted separately"""
    if not matrix or not matrix[0]:
        return False
    
    row, col = 0, len(matrix[0]) - 1
    
    while row < len(matrix) and col >= 0:
        if matrix[row][col] == target:
            return True
        elif matrix[row][col] > target:
            col -= 1
        else:
            row += 1
    
    return False

Binary Search on Answer (Optimization Problems):

# Minimum Speed to Eat Bananas
def min_eating_speed(piles, hours):
    """Find minimum eating speed to finish all bananas within hours"""
    def can_finish(speed):
        import math
        time_needed = sum(math.ceil(pile / speed) for pile in piles)
        return time_needed <= hours
    
    left, right = 1, max(piles)
    
    while left < right:
        mid = left + (right - left) // 2
        
        if can_finish(mid):
            right = mid
        else:
            left = mid + 1
    
    return left

# Capacity to Ship Packages
def ship_within_days(weights, days):
    """Find minimum ship capacity to ship all packages within days"""
    def can_ship(capacity):
        days_needed = 1
        current_weight = 0
        
        for weight in weights:
            if current_weight + weight > capacity:
                days_needed += 1
                current_weight = weight
            else:
                current_weight += weight
        
        return days_needed <= days
    
    left, right = max(weights), sum(weights)
    
    while left < right:
        mid = left + (right - left) // 2
        
        if can_ship(mid):
            right = mid
        else:
            left = mid + 1
    
    return left

# Split Array Largest Sum
def split_array(nums, m):
    """Split array into m subarrays to minimize largest sum"""
    def can_split(max_sum):
        subarrays = 1
        current_sum = 0
        
        for num in nums:
            if current_sum + num > max_sum:
                subarrays += 1
                current_sum = num
            else:
                current_sum += num
        
        return subarrays <= m
    
    left, right = max(nums), sum(nums)
    
    while left < right:
        mid = left + (right - left) // 2
        
        if can_split(mid):
            right = mid
        else:
            left = mid + 1
    
    return left

# Find Kth Smallest Element in Sorted Matrix
def kth_smallest_matrix(matrix, k):
    """Find kth smallest element in sorted matrix"""
    def count_less_equal(target):
        count = 0
        row, col = len(matrix) - 1, 0
        
        while row >= 0 and col < len(matrix[0]):
            if matrix[row][col] <= target:
                count += row + 1
                col += 1
            else:
                row -= 1
        
        return count
    
    left, right = matrix[0][0], matrix[-1][-1]
    
    while left < right:
        mid = left + (right - left) // 2
        
        if count_less_equal(mid) < k:
            left = mid + 1
        else:
            right = mid
    
    return left

# Aggressive Cows / Magnetic Force
def max_distance(positions, m):
    """Place m balls to maximize minimum distance between any two balls"""
    positions.sort()
    
    def can_place(min_dist):
        count = 1
        last_pos = positions[0]
        
        for pos in positions[1:]:
            if pos - last_pos >= min_dist:
                count += 1
                last_pos = pos
        
        return count >= m
    
    left, right = 1, positions[-1] - positions[0]
    
    while left < right:
        mid = left + (right - left + 1) // 2  # Bias towards right
        
        if can_place(mid):
            left = mid
        else:
            right = mid - 1
    
    return left

Pro Tips:

  • Always use left + (right - left) // 2 to avoid integer overflow
  • For "find first/last valid position", use the template pattern
  • Binary search on answer is powerful for optimization problems
  • Consider the search space carefully - it doesn't have to be array indices
  • Pay attention to boundary conditions and off-by-one errors
  • When the condition function is expensive, binary search provides logarithmic complexity

3. Greedy

Concepts:

  • Make locally optimal choices that lead to global optimum
  • Greedy choice property and optimal substructure
  • Often involves sorting as preprocessing step
  • Common in interval problems, scheduling, and optimization

Essential Greedy Templates:

# Activity Selection / Interval Scheduling
def max_non_overlapping_intervals(intervals):
    """Select maximum number of non-overlapping intervals"""
    if not intervals:
        return 0
    
    # Sort by end time (greedy choice)
    intervals.sort(key=lambda x: x[1])
    
    count = 1
    last_end = intervals[0][1]
    
    for start, end in intervals[1:]:
        if start >= last_end:  # Non-overlapping
            count += 1
            last_end = end
    
    return count

# Minimum number of arrows to burst balloons
def find_min_arrows(points):
    """Find minimum arrows needed to burst all balloons"""
    if not points:
        return 0
    
    points.sort(key=lambda x: x[1])  # Sort by end position
    
    arrows = 1
    arrow_pos = points[0][1]
    
    for start, end in points[1:]:
        if start > arrow_pos:  # Need new arrow
            arrows += 1
            arrow_pos = end
    
    return arrows

# Jump Game
def can_jump(nums):
    """Check if you can reach the last index"""
    max_reach = 0
    
    for i in range(len(nums)):
        if i > max_reach:
            return False
        max_reach = max(max_reach, i + nums[i])
        if max_reach >= len(nums) - 1:
            return True
    
    return True

def jump_game_min_jumps(nums):
    """Find minimum number of jumps to reach the end"""
    if len(nums) <= 1:
        return 0
    
    jumps = 0
    current_end = 0
    farthest = 0
    
    for i in range(len(nums) - 1):
        farthest = max(farthest, i + nums[i])
        
        if i == current_end:
            jumps += 1
            current_end = farthest
            
            if current_end >= len(nums) - 1:
                break
    
    return jumps

Advanced Greedy Patterns:

# Gas Station Problem
def can_complete_circuit(gas, cost):
    """Find starting gas station to complete circular tour"""
    total_gas = sum(gas)
    total_cost = sum(cost)
    
    if total_gas < total_cost:
        return -1
    
    current_gas = 0
    start = 0
    
    for i in range(len(gas)):
        current_gas += gas[i] - cost[i]
        
        if current_gas < 0:
            # Can't reach next station, try starting from next station
            start = i + 1
            current_gas = 0
    
    return start

# Assign Cookies
def find_content_children(children, cookies):
    """Assign cookies to children to maximize satisfied children"""
    children.sort()
    cookies.sort()
    
    child = cookie = 0
    satisfied = 0
    
    while child < len(children) and cookie < len(cookies):
        if cookies[cookie] >= children[child]:
            satisfied += 1
            child += 1
        cookie += 1
    
    return satisfied

# Task Scheduler
def least_interval(tasks, n):
    """Find minimum time to complete all tasks with cooling period"""
    from collections import Counter
    import heapq
    
    task_count = Counter(tasks)
    max_heap = [-count for count in task_count.values()]
    heapq.heapify(max_heap)
    
    time = 0
    
    while max_heap:
        cycle = []
        
        # Process one cooling cycle
        for _ in range(n + 1):
            if max_heap:
                cycle.append(heapq.heappop(max_heap))
        
        # Add back tasks that still need processing
        for count in cycle:
            if count < -1:  # Still has remaining tasks
                heapq.heappush(max_heap, count + 1)
        
        # Add time for this cycle
        time += (n + 1) if max_heap else len(cycle)
    
    return time

# Reorganize String
def reorganize_string(s):
    """Reorganize string so no two adjacent characters are same"""
    from collections import Counter
    import heapq
    
    char_count = Counter(s)
    max_heap = [(-count, char) for char, count in char_count.items()]
    heapq.heapify(max_heap)
    
    result = []
    prev_count, prev_char = 0, ''
    
    while max_heap:
        count, char = heapq.heappop(max_heap)
        result.append(char)
        
        # Add back previous character if it still has remaining count
        if prev_count < 0:
            heapq.heappush(max_heap, (prev_count, prev_char))
        
        prev_count, prev_char = count + 1, char
    
    result_str = ''.join(result)
    return result_str if len(result_str) == len(s) else ""

# Remove K Digits
def remove_k_digits(num, k):
    """Remove k digits to make the smallest possible number"""
    stack = []
    
    for digit in num:
        # Remove larger digits from the end while we can
        while stack and stack[-1] > digit and k > 0:
            stack.pop()
            k -= 1
        stack.append(digit)
    
    # Remove remaining digits from the end
    while k > 0:
        stack.pop()
        k -= 1
    
    # Build result and handle leading zeros
    result = ''.join(stack).lstrip('0')
    return result if result else '0'

# Queue Reconstruction by Height
def reconstruct_queue(people):
    """Reconstruct queue based on height and number of taller people in front"""
    # Sort by height desc, then by count asc
    people.sort(key=lambda x: (-x[0], x[1]))
    
    result = []
    for person in people:
        # Insert at position equal to count of taller people
        result.insert(person[1], person)
    
    return result

# Candy Distribution
def min_candy(ratings):
    """Distribute minimum candies such that higher rating gets more candy than neighbors"""
    n = len(ratings)
    candies = [1] * n
    
    # Left to right pass
    for i in range(1, n):
        if ratings[i] > ratings[i-1]:
            candies[i] = candies[i-1] + 1
    
    # Right to left pass
    for i in range(n-2, -1, -1):
        if ratings[i] > ratings[i+1]:
            candies[i] = max(candies[i], candies[i+1] + 1)
    
    return sum(candies)

# Minimum Cost to Connect Sticks
def connect_sticks(sticks):
    """Find minimum cost to connect all sticks"""
    import heapq
    
    if len(sticks) <= 1:
        return 0
    
    heapq.heapify(sticks)
    total_cost = 0
    
    while len(sticks) > 1:
        first = heapq.heappop(sticks)
        second = heapq.heappop(sticks)
        cost = first + second
        total_cost += cost
        heapq.heappush(sticks, cost)
    
    return total_cost

Pro Tips:

  • Greedy algorithms require proof of correctness - verify greedy choice property
  • Sorting is often the key preprocessing step
  • Consider what metric to optimize (earliest end time, smallest size, etc.)
  • Greedy doesn't always work - use when local optimum leads to global optimum
  • Common greedy strategies: earliest deadline first, smallest first, largest first
  • Combine with other techniques: priority queues, Union-Find, dynamic programming

4. Stack / Monotonic Stack

Concepts:

  • LIFO (Last In, First Out) data structure
  • Function call stack, expression evaluation, backtracking
  • Monotonic stack: maintain elements in increasing/decreasing order
  • Powerful for "next greater/smaller" element problems

Essential Stack Templates:

# Basic stack operations using list
stack = []
stack.append(item)    # Push
item = stack.pop()    # Pop (raises IndexError if empty)
top = stack[-1]       # Peek (top element)
is_empty = len(stack) == 0

# Valid Parentheses
def is_valid_parentheses(s):
    """Check if parentheses are properly matched"""
    stack = []
    mapping = {')': '(', '}': '{', ']': '['}
    
    for char in s:
        if char in mapping:  # Closing bracket
            if not stack or stack.pop() != mapping[char]:
                return False
        else:  # Opening bracket
            stack.append(char)
    
    return len(stack) == 0

# Evaluate Reverse Polish Notation
def eval_rpn(tokens):
    """Evaluate expression in Reverse Polish Notation"""
    stack = []
    operators = {'+', '-', '*', '/'}
    
    for token in tokens:
        if token in operators:
            b = stack.pop()
            a = stack.pop()
            
            if token == '+':
                stack.append(a + b)
            elif token == '-':
                stack.append(a - b)
            elif token == '*':
                stack.append(a * b)
            elif token == '/':
                # Truncate towards zero
                stack.append(int(a / b))
        else:
            stack.append(int(token))
    
    return stack[0]

# Min Stack (stack with O(1) min operation)
class MinStack:
    def __init__(self):
        self.stack = []
        self.min_stack = []
    
    def push(self, val):
        self.stack.append(val)
        if not self.min_stack or val <= self.min_stack[-1]:
            self.min_stack.append(val)
    
    def pop(self):
        val = self.stack.pop()
        if val == self.min_stack[-1]:
            self.min_stack.pop()
        return val
    
    def top(self):
        return self.stack[-1]
    
    def get_min(self):
        return self.min_stack[-1]

Monotonic Stack Patterns:

# Next Greater Element
def next_greater_elements(nums):
    """Find next greater element for each element"""
    n = len(nums)
    result = [-1] * n
    stack = []  # Store indices
    
    for i in range(n):
        # Pop elements that are smaller than current
        while stack and nums[stack[-1]] < nums[i]:
            result[stack.pop()] = nums[i]
        
        stack.append(i)
    
    return result

# Next Greater Element in Circular Array
def next_greater_elements_circular(nums):
    """Next greater element in circular array"""
    n = len(nums)
    result = [-1] * n
    stack = []
    
    # Process array twice to handle circular nature
    for i in range(2 * n):
        actual_i = i % n
        
        while stack and nums[stack[-1]] < nums[actual_i]:
            result[stack.pop()] = nums[actual_i]
        
        if i < n:  # Only push indices from first iteration
            stack.append(actual_i)
    
    return result

# Daily Temperatures
def daily_temperatures(temperatures):
    """Find how many days until warmer temperature"""
    result = [0] * len(temperatures)
    stack = []  # Store indices
    
    for i, temp in enumerate(temperatures):
        while stack and temperatures[stack[-1]] < temp:
            prev_i = stack.pop()
            result[prev_i] = i - prev_i
        
        stack.append(i)
    
    return result

# Largest Rectangle in Histogram
def largest_rectangle_area(heights):
    """Find area of largest rectangle in histogram"""
    stack = []
    max_area = 0
    
    for i in range(len(heights)):
        # Pop heights greater than current height
        while stack and heights[stack[-1]] > heights[i]:
            h = heights[stack.pop()]
            # Width is from stack top to current index
            w = i if not stack else i - stack[-1] - 1
            max_area = max(max_area, h * w)
        
        stack.append(i)
    
    # Process remaining heights in stack
    while stack:
        h = heights[stack.pop()]
        w = len(heights) if not stack else len(heights) - stack[-1] - 1
        max_area = max(max_area, h * w)
    
    return max_area

# Maximal Rectangle in Binary Matrix
def maximal_rectangle(matrix):
    """Find largest rectangle in binary matrix"""
    if not matrix or not matrix[0]:
        return 0
    
    rows, cols = len(matrix), len(matrix[0])
    heights = [0] * cols
    max_area = 0
    
    for row in matrix:
        # Update heights for current row
        for i in range(cols):
            heights[i] = heights[i] + 1 if row[i] == '1' else 0
        
        # Find largest rectangle in current histogram
        max_area = max(max_area, largest_rectangle_area(heights))
    
    return max_area

# Trapping Rain Water
def trap_rainwater(heights):
    """Calculate trapped rainwater using stack"""
    stack = []
    water = 0
    
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] < h:
            bottom = stack.pop()
            
            if not stack:
                break
            
            # Calculate trapped water
            width = i - stack[-1] - 1
            height = min(heights[stack[-1]], h) - heights[bottom]
            water += width * height
        
        stack.append(i)
    
    return water

Advanced Stack Applications:

# Remove K Digits (using stack)
def remove_k_digits(num, k):
    """Remove k digits to form smallest number"""
    stack = []
    
    for digit in num:
        # Remove larger digits while we can
        while stack and stack[-1] > digit and k > 0:
            stack.pop()
            k -= 1
        stack.append(digit)
    
    # Remove remaining digits from end
    while k > 0:
        stack.pop()
        k -= 1
    
    # Build result, handling leading zeros
    result = ''.join(stack).lstrip('0')
    return result if result else '0'

# Remove Duplicate Letters
def remove_duplicate_letters(s):
    """Remove duplicates to get lexicographically smallest result"""
    from collections import Counter
    
    char_count = Counter(s)
    in_stack = set()
    stack = []
    
    for char in s:
        char_count[char] -= 1
        
        if char in in_stack:
            continue
        
        # Remove characters that are larger and appear later
        while (stack and stack[-1] > char and 
               char_count[stack[-1]] > 0):
            removed = stack.pop()
            in_stack.remove(removed)
        
        stack.append(char)
        in_stack.add(char)
    
    return ''.join(stack)

# Decode String
def decode_string(s):
    """Decode string with pattern k[encoded_string]"""
    stack = []
    current_num = 0
    current_str = ""
    
    for char in s:
        if char.isdigit():
            current_num = current_num * 10 + int(char)
        elif char == '[':
            # Push current state to stack
            stack.append((current_str, current_num))
            current_str = ""
            current_num = 0
        elif char == ']':
            # Pop and construct string
            prev_str, num = stack.pop()
            current_str = prev_str + current_str * num
        else:
            current_str += char
    
    return current_str

# Asteroid Collision
def asteroid_collision(asteroids):
    """Simulate asteroid collisions"""
    stack = []
    
    for asteroid in asteroids:
        while stack and asteroid < 0 < stack[-1]:
            # Collision occurs
            if stack[-1] < -asteroid:
                stack.pop()  # Right asteroid destroys left
                continue
            elif stack[-1] == -asteroid:
                stack.pop()  # Both destroyed
            break  # Left asteroid survives or both destroyed
        else:
            stack.append(asteroid)
    
    return stack

# Car Fleet
def car_fleet(target, position, speed):
    """Calculate number of car fleets reaching target"""
    cars = sorted(zip(position, speed), reverse=True)
    stack = []
    
    for pos, spd in cars:
        time_to_target = (target - pos) / spd
        
        # If this car is slower than the one in front, it forms a fleet
        if not stack or time_to_target > stack[-1]:
            stack.append(time_to_target)
    
    return len(stack)

# Sum of Subarray Minimums
def sum_subarray_mins(arr):
    """Sum of minimums of all subarrays"""
    MOD = 10**9 + 7
    n = len(arr)
    
    # Find previous smaller element for each element
    prev_smaller = [-1] * n
    stack = []
    for i in range(n):
        while stack and arr[stack[-1]] > arr[i]:
            stack.pop()
        prev_smaller[i] = stack[-1] if stack else -1
        stack.append(i)
    
    # Find next smaller element for each element
    next_smaller = [n] * n
    stack = []
    for i in range(n-1, -1, -1):
        while stack and arr[stack[-1]] >= arr[i]:
            stack.pop()
        next_smaller[i] = stack[-1] if stack else n
        stack.append(i)
    
    # Calculate sum
    result = 0
    for i in range(n):
        left_count = i - prev_smaller[i]
        right_count = next_smaller[i] - i
        result = (result + arr[i] * left_count * right_count) % MOD
    
    return result

Stack Pattern Recognition:

def monotonic_stack_template(arr, find_next_greater=True):
    """
    Generic monotonic stack template
    
    Args:
        arr: Input array
        find_next_greater: True for next greater, False for next smaller
    """
    result = [-1] * len(arr)
    stack = []  # Store indices
    
    for i in range(len(arr)):
        # Pop elements based on what we're looking for
        while stack:
            if find_next_greater and arr[stack[-1]] < arr[i]:
                result[stack.pop()] = arr[i]
            elif not find_next_greater and arr[stack[-1]] > arr[i]:
                result[stack.pop()] = arr[i]
            else:
                break
        
        stack.append(i)
    
    return result

# When to use each approach:
# 1. Basic stack: Expression evaluation, parentheses matching
# 2. Monotonic increasing: Next smaller element, trapping water
# 3. Monotonic decreasing: Next greater element, daily temperatures
# 4. Min/Max stack: When you need O(1) min/max operations

Pro Tips:

  • Use indices in monotonic stack to calculate distances
  • Stack problems often involve "nearest" or "next" relationships
  • For histogram problems, think about expanding rectangles
  • Monotonic stack reduces O(nΒ²) brute force to O(n)
  • Consider what you're maintaining in the stack: values or indices
  • Stack + recursion can solve complex parsing problems

5. Heap (Priority Queue)

Concepts:

  • Min-heap by default in Python
  • Max-heap by pushing negative
  • Kth largest/smallest, merge k lists
  • Running median, top-k frequent, task scheduler

Advanced Patterns:

import heapq

# Min heap
heap = []
heapq.heappush(heap, 3)
heapq.heappop(heap)

# Max heap using negation
heapq.heappush(heap, -num)
max_val = -heapq.heappop(heap)

# Top-k elements
heapq.nlargest(k, arr)
heapq.nsmallest(k, arr)

# Running median with two heaps
class MedianFinder:
    def __init__(self):
        self.small = []  # max heap (negate values)
        self.large = []  # min heap
    
    def addNum(self, num):
        heapq.heappush(self.small, -num)
        heapq.heappush(self.large, -heapq.heappop(self.small))
        if len(self.large) > len(self.small):
            heapq.heappush(self.small, -heapq.heappop(self.large))
    
    def findMedian(self):
        if len(self.small) > len(self.large):
            return -self.small[0]
        return (-self.small[0] + self.large[0]) / 2

# Merge k sorted lists
def mergeKLists(lists):
    heap = []
    for i, lst in enumerate(lists):
        if lst:
            heapq.heappush(heap, (lst.val, i, lst))
    
    dummy = ListNode(0)
    cur = dummy
    while heap:
        val, i, node = heapq.heappop(heap)
        cur.next = node
        cur = cur.next
        if node.next:
            heapq.heappush(heap, (node.next.val, i, node.next))
    return dummy.next

# Task scheduler (cooling time)
def leastInterval(tasks, n):
    count = Counter(tasks)
    heap = [-cnt for cnt in count.values()]
    heapq.heapify(heap)
    
    time = 0
    q = deque()  # (count, idle_time)
    
    while heap or q:
        time += 1
        if heap:
            cnt = 1 + heapq.heappop(heap)
            if cnt:
                q.append([cnt, time + n])
        if q and q[0][1] == time:
            heapq.heappush(heap, q.popleft()[0])
    return time

# Kth largest element in stream
class KthLargest:
    def __init__(self, k, nums):
        self.k = k
        self.heap = nums
        heapq.heapify(self.heap)
        while len(self.heap) > k:
            heapq.heappop(self.heap)
    
    def add(self, val):
        heapq.heappush(self.heap, val)
        if len(self.heap) > self.k:
            heapq.heappop(self.heap)
        return self.heap[0]

Pro Tips:

  • Use heap for "top-k" or "streaming" problems
  • Two-heap technique for running median
  • Always consider if min-heap or max-heap fits better
  • For custom objects, use tuples: (priority, item)

6. Linked List

Concepts:

  • Pointer manipulation, dummy head
  • Two-pointers (slow/fast)
  • Cycle detection
  • Reverse, merge, split

Advanced Patterns:

# Linked List Node
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# Reverse linked list
def reverseList(head):
    prev = None
    cur = head
    while cur:
        next_temp = cur.next
        cur.next = prev
        prev = cur
        cur = next_temp
    return prev

# Reverse nodes in k-group
def reverseKGroup(head, k):
    def reverseGroup(start, end):
        prev = None
        cur = start
        while cur != end:
            next_temp = cur.next
            cur.next = prev
            prev = cur
            cur = next_temp
        return prev
    
    dummy = ListNode(0)
    dummy.next = head
    prev_group = dummy
    
    while True:
        start = prev_group.next
        end = start
        for _ in range(k):
            if not end:
                return dummy.next
            end = end.next
        
        next_group = end
        new_start = reverseGroup(start, end)
        prev_group.next = new_start
        start.next = next_group
        prev_group = start
    
    return dummy.next

# Merge two sorted lists
def mergeTwoLists(l1, l2):
    dummy = ListNode(0)
    cur = dummy
    while l1 and l2:
        if l1.val <= l2.val:
            cur.next = l1
            l1 = l1.next
        else:
            cur.next = l2
            l2 = l2.next
        cur = cur.next
    cur.next = l1 or l2
    return dummy.next

# Cycle detection (Floyd's Algorithm)
def hasCycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False

# Find cycle start
def detectCycle(head):
    slow = fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            break
    else:
        return None
    
    # Find start of cycle
    while head != slow:
        head = head.next
        slow = slow.next
    return head

# Remove nth node from end
def removeNthFromEnd(head, n):
    dummy = ListNode(0)
    dummy.next = head
    first = second = dummy
    
    # Move first n+1 steps ahead
    for _ in range(n + 1):
        first = first.next
    
    # Move both until first reaches end
    while first:
        first = first.next
        second = second.next
    
    second.next = second.next.next
    return dummy.next

# Reorder list (L0 -> L1 -> ... -> Ln-1 -> Ln to L0 -> Ln -> L1 -> Ln-1 -> ...)
def reorderList(head):
    if not head or not head.next:
        return
    
    # Find middle
    slow = fast = head
    while fast.next and fast.next.next:
        slow = slow.next
        fast = fast.next.next
    
    # Split and reverse second half
    second = slow.next
    slow.next = None
    second = reverseList(second)
    
    # Merge two halves
    first = head
    while second:
        temp1, temp2 = first.next, second.next
        first.next = second
        second.next = temp1
        first, second = temp1, temp2

# Add two numbers (stored in reverse order)
def addTwoNumbers(l1, l2):
    dummy = ListNode(0)
    cur = dummy
    carry = 0
    
    while l1 or l2 or carry:
        val1 = l1.val if l1 else 0
        val2 = l2.val if l2 else 0
        total = val1 + val2 + carry
        
        carry = total // 10
        cur.next = ListNode(total % 10)
        cur = cur.next
        
        if l1: l1 = l1.next
        if l2: l2 = l2.next
    
    return dummy.next

# Copy list with random pointer
def copyRandomList(head):
    if not head:
        return None
    
    # Create new nodes and insert them after original nodes
    cur = head
    while cur:
        new_node = ListNode(cur.val)
        new_node.next = cur.next
        cur.next = new_node
        cur = new_node.next
    
    # Set random pointers for new nodes
    cur = head
    while cur:
        if cur.random:
            cur.next.random = cur.random.next
        cur = cur.next.next
    
    # Extract the new list
    dummy = ListNode(0)
    cur_new = dummy
    cur = head
    
    while cur:
        cur_new.next = cur.next
        cur.next = cur.next.next
        cur = cur.next
        cur_new = cur_new.next
    
    return dummy.next

Pro Tips:

  • Always use dummy head for easier manipulation
  • Two-pointer technique for many problems (cycle, middle, nth from end)
  • Draw diagrams to visualize pointer movements
  • Handle edge cases: empty list, single node
  • For complex operations, break into smaller steps

6. Math

Concepts:

  • GCD, LCM, prime checking, modular arithmetic
  • Number digit manipulation
  • Combinatorics (factorials, nCr)
  • Mathematical optimization and number theory

Advanced Mathematical Patterns:

import math
from math import gcd, lcm, comb, factorial

# Prime checking (optimized)
def is_prime(n):
    if n < 2: return False
    if n == 2: return True
    if n % 2 == 0: return False
    for i in range(3, int(n**0.5) + 1, 2):
        if n % i == 0: return False
    return True

# Sieve of Eratosthenes
def sieve_of_eratosthenes(n):
    """Generate all primes up to n"""
    is_prime = [True] * (n + 1)
    is_prime[0] = is_prime[1] = False
    
    for i in range(2, int(n**0.5) + 1):
        if is_prime[i]:
            for j in range(i*i, n + 1, i):
                is_prime[j] = False
    
    return [i for i in range(n + 1) if is_prime[i]]

# Prime factorization
def prime_factors(n):
    """Get prime factors of n"""
    factors = []
    d = 2
    while d * d <= n:
        while n % d == 0:
            factors.append(d)
            n //= d
        d += 1
    if n > 1:
        factors.append(n)
    return factors

# Extended Euclidean Algorithm
def extended_gcd(a, b):
    """Return gcd(a,b) and coefficients x,y such that ax + by = gcd(a,b)"""
    if a == 0:
        return b, 0, 1
    gcd_val, x1, y1 = extended_gcd(b % a, a)
    x = y1 - (b // a) * x1
    y = x1
    return gcd_val, x, y

# Modular arithmetic
def mod_inverse(a, m):
    """Find modular inverse of a under modulo m"""
    gcd_val, x, _ = extended_gcd(a, m)
    if gcd_val != 1:
        return None  # Inverse doesn't exist
    return (x % m + m) % m

def mod_pow(base, exp, mod):
    """Calculate (base^exp) % mod efficiently"""
    result = 1
    base = base % mod
    while exp > 0:
        if exp % 2 == 1:
            result = (result * base) % mod
        exp = exp >> 1
        base = (base * base) % mod
    return result

# Number manipulation
def reverse_integer(x):
    """Reverse digits of integer"""
    sign = -1 if x < 0 else 1
    x = abs(x)
    reversed_num = 0
    while x:
        reversed_num = reversed_num * 10 + x % 10
        x //= 10
    return sign * reversed_num

def count_digits(n):
    """Count number of digits"""
    return len(str(abs(n)))

def sum_of_digits(n):
    """Sum of digits"""
    total = 0
    n = abs(n)
    while n:
        total += n % 10
        n //= 10
    return total

# Advanced combinatorics
def nCr_mod(n, r, mod):
    """Calculate nCr % mod using Lucas theorem or precomputed factorials"""
    if r > n or r < 0:
        return 0
    if r == 0 or r == n:
        return 1
    
    # Using Pascal's triangle for small values
    if n <= 1000:
        dp = [[0] * (r + 1) for _ in range(n + 1)]
        for i in range(n + 1):
            dp[i][0] = 1
        
        for i in range(1, n + 1):
            for j in range(1, min(i + 1, r + 1)):
                dp[i][j] = (dp[i-1][j-1] + dp[i-1][j]) % mod
        
        return dp[n][r]
    
    # For larger values, use modular arithmetic
    num = 1
    den = 1
    for i in range(r):
        num = (num * (n - i)) % mod
        den = (den * (i + 1)) % mod
    
    return (num * mod_inverse(den, mod)) % mod

# Catalan numbers
def catalan_number(n):
    """Calculate nth Catalan number"""
    if n <= 1:
        return 1
    
    catalan = [0] * (n + 1)
    catalan[0] = catalan[1] = 1
    
    for i in range(2, n + 1):
        for j in range(i):
            catalan[i] += catalan[j] * catalan[i - 1 - j]
    
    return catalan[n]

# Perfect squares and cubes
def is_perfect_square(n):
    """Check if n is a perfect square"""
    if n < 0:
        return False
    root = int(n**0.5)
    return root * root == n

def sqrt_integer(x):
    """Integer square root using binary search"""
    if x < 2:
        return x
    
    left, right = 1, x
    while left <= right:
        mid = left + (right - left) // 2
        square = mid * mid
        
        if square == x:
            return mid
        elif square < x:
            left = mid + 1
        else:
            right = mid - 1
    
    return right

# Geometry basics
def distance(p1, p2):
    """Euclidean distance between two points"""
    return ((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)**0.5

def area_triangle(p1, p2, p3):
    """Area of triangle using coordinates"""
    return abs((p1[0]*(p2[1] - p3[1]) + p2[0]*(p3[1] - p1[1]) + p3[0]*(p1[1] - p2[1])) / 2)

# Number theory applications
def happy_number(n):
    """Check if number is happy (sum of squares of digits eventually reaches 1)"""
    def get_sum_of_squares(num):
        total = 0
        while num:
            digit = num % 10
            total += digit * digit
            num //= 10
        return total
    
    seen = set()
    while n != 1 and n not in seen:
        seen.add(n)
        n = get_sum_of_squares(n)
    
    return n == 1

def ugly_number(n):
    """Check if number's only prime factors are 2, 3, 5"""
    if n <= 0:
        return False
    
    for factor in [2, 3, 5]:
        while n % factor == 0:
            n //= factor
    
    return n == 1

Pro Tips:

  • Use built-in math.gcd() and math.lcm() for efficiency
  • For large numbers, always consider modular arithmetic
  • Sieve of Eratosthenes for multiple prime queries
  • Binary search can solve many mathematical optimization problems
  • Handle edge cases: negative numbers, zero, overflow

7. Simulation

Concepts:

  • Mimic the problem as described
  • No special algorithm, just accurate implementation
  • Track state changes, position updates, and game mechanics
  • Common in game simulations, robot movement, and state machines

Advanced Simulation Patterns:

# Matrix rotation/transformation
def rotate_matrix_90(matrix):
    """Rotate matrix 90 degrees clockwise"""
    n = len(matrix)
    # Transpose
    for i in range(n):
        for j in range(i, n):
            matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
    
    # Reverse each row
    for i in range(n):
        matrix[i].reverse()
    
    return matrix

def spiral_matrix(matrix):
    """Traverse matrix in spiral order"""
    if not matrix or not matrix[0]:
        return []
    
    result = []
    top, bottom = 0, len(matrix) - 1
    left, right = 0, len(matrix[0]) - 1
    
    while top <= bottom and left <= right:
        # Right
        for col in range(left, right + 1):
            result.append(matrix[top][col])
        top += 1
        
        # Down
        for row in range(top, bottom + 1):
            result.append(matrix[row][right])
        right -= 1
        
        # Left
        if top <= bottom:
            for col in range(right, left - 1, -1):
                result.append(matrix[bottom][col])
            bottom -= 1
        
        # Up
        if left <= right:
            for row in range(bottom, top - 1, -1):
                result.append(matrix[row][left])
            left += 1
    
    return result

# Robot/Game simulation
def robot_bounded_circle(instructions):
    """Check if robot moves in a circle"""
    x, y = 0, 0
    dx, dy = 0, 1  # Initially facing north
    
    for instruction in instructions:
        if instruction == 'G':
            x, y = x + dx, y + dy
        elif instruction == 'L':
            dx, dy = -dy, dx
        elif instruction == 'R':
            dx, dy = dy, -dx
    
    # Robot is bounded if it's at origin OR not facing north
    return (x == 0 and y == 0) or (dx != 0 or dy != 1)

# Conway's Game of Life
def game_of_life(board):
    """Simulate one step of Conway's Game of Life"""
    m, n = len(board), len(board[0])
    
    def count_neighbors(r, c):
        count = 0
        for dr in [-1, 0, 1]:
            for dc in [-1, 0, 1]:
                if dr == 0 and dc == 0:
                    continue
                nr, nc = r + dr, c + dc
                if 0 <= nr < m and 0 <= nc < n and abs(board[nr][nc]) == 1:
                    count += 1
        return count
    
    # Use different values to track state changes
    for i in range(m):
        for j in range(n):
            neighbors = count_neighbors(i, j)
            
            if board[i][j] == 1:  # Live cell
                if neighbors < 2 or neighbors > 3:
                    board[i][j] = -1  # Dies
            else:  # Dead cell
                if neighbors == 3:
                    board[i][j] = 2  # Becomes alive
    
    # Update to final state
    for i in range(m):
        for j in range(n):
            if board[i][j] > 0:
                board[i][j] = 1
            else:
                board[i][j] = 0

# Tic-tac-toe game
class TicTacToe:
    def __init__(self, n):
        self.n = n
        self.rows = [0] * n
        self.cols = [0] * n
        self.diagonal = 0
        self.anti_diagonal = 0
    
    def move(self, row, col, player):
        """Make a move and return winner (0 if no winner yet)"""
        add = 1 if player == 1 else -1
        
        self.rows[row] += add
        self.cols[col] += add
        
        if row == col:
            self.diagonal += add
        
        if row + col == self.n - 1:
            self.anti_diagonal += add
        
        # Check for win
        if (abs(self.rows[row]) == self.n or 
            abs(self.cols[col]) == self.n or 
            abs(self.diagonal) == self.n or 
            abs(self.anti_diagonal) == self.n):
            return player
        
        return 0

# Parking system simulation
class ParkingSystem:
    def __init__(self, big, medium, small):
        self.spaces = [0, big, medium, small]  # Index 0 unused
    
    def addCar(self, carType):
        if self.spaces[carType] > 0:
            self.spaces[carType] -= 1
            return True
        return False

# Stock trading simulation
def max_profit_with_cooldown(prices):
    """Stock trading with cooldown period"""
    if not prices:
        return 0
    
    # held[i]: max profit when holding stock on day i
    # sold[i]: max profit when not holding stock on day i (just sold)
    # rest[i]: max profit when not holding stock on day i (didn't sell)
    
    held = -prices[0]
    sold = 0
    rest = 0
    
    for i in range(1, len(prices)):
        prev_held, prev_sold, prev_rest = held, sold, rest
        
        held = max(prev_held, prev_rest - prices[i])
        sold = prev_held + prices[i]
        rest = max(prev_rest, prev_sold)
    
    return max(sold, rest)

Pro Tips:

  • Break complex simulations into smaller state updates
  • Use appropriate data structures (sets for O(1) lookup, deques for queues)
  • Track all state variables carefully
  • Consider using state machines for complex logic
  • Test edge cases thoroughly (boundaries, empty states, etc.)

6. Counting

Concepts:

  • Count how many values satisfy a condition
  • Usually used with hash maps or prefix sum
  • Combinatorial counting and mathematical analysis
  • Often combined with other techniques for optimization

Advanced Counting Patterns:

# Subarray sum equals k
def subarray_sum_k(nums, k):
    """Count number of subarrays with sum equal to k"""
    count = 0
    prefix_sum = 0
    prefix_count = {0: 1}  # Empty prefix has sum 0
    
    for num in nums:
        prefix_sum += num
        # Check if (prefix_sum - k) exists
        count += prefix_count.get(prefix_sum - k, 0)
        prefix_count[prefix_sum] = prefix_count.get(prefix_sum, 0) + 1
    
    return count

# Count number of nice subarrays (exactly k odd numbers)
def number_of_subarrays(nums, k):
    """Count subarrays with exactly k odd numbers"""
    # Convert to binary: 1 for odd, 0 for even
    binary = [x % 2 for x in nums]
    return subarray_sum_k(binary, k)

# Count vowel strings
def count_vowel_strings(n):
    """Count strings of length n using only vowels in lexicographical order"""
    # dp[i][j] = number of strings of length i ending with vowel j
    vowels = 5  # a, e, i, o, u
    dp = [1] * vowels
    
    for i in range(2, n + 1):
        for j in range(vowels - 2, -1, -1):
            dp[j] += dp[j + 1]
    
    return sum(dp)

# Count binary strings without consecutive 1s
def count_binary_strings(n):
    """Count n-length binary strings with no consecutive 1s"""
    if n == 1:
        return 2
    
    # dp[i][0] = strings of length i ending with 0
    # dp[i][1] = strings of length i ending with 1
    prev_0, prev_1 = 1, 1
    
    for i in range(2, n + 1):
        curr_0 = prev_0 + prev_1  # Can append 0 to any string
        curr_1 = prev_0           # Can only append 1 to strings ending with 0
        prev_0, prev_1 = curr_0, curr_1
    
    return prev_0 + prev_1

# Count palindromic subsequences
def count_palindromic_subsequences(s):
    """Count distinct palindromic subsequences"""
    n = len(s)
    dp = [[0] * n for _ in range(n)]
    
    # Single characters are palindromes
    for i in range(n):
        dp[i][i] = 1
    
    # Length 2 substrings
    for i in range(n - 1):
        dp[i][i + 1] = 2 if s[i] != s[i + 1] else 1
    
    # Length 3 and above
    for length in range(3, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            
            if s[i] == s[j]:
                dp[i][j] = dp[i + 1][j - 1] + 2
            else:
                dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1]
    
    return dp[0][n - 1]

# Count islands in 2D grid
def count_islands(grid):
    """Count number of islands in binary grid"""
    if not grid or not grid[0]:
        return 0
    
    m, n = len(grid), len(grid[0])
    count = 0
    
    def dfs(i, j):
        if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != '1':
            return
        grid[i][j] = '0'  # Mark as visited
        for di, dj in [(0, 1), (1, 0), (0, -1), (-1, 0)]:
            dfs(i + di, j + dj)
    
    for i in range(m):
        for j in range(n):
            if grid[i][j] == '1':
                count += 1
                dfs(i, j)
    
    return count

# Count valid parentheses combinations
def generate_parentheses(n):
    """Generate all valid parentheses combinations"""
    result = []
    
    def backtrack(current, open_count, close_count):
        if len(current) == 2 * n:
            result.append(current)
            return
        
        if open_count < n:
            backtrack(current + '(', open_count + 1, close_count)
        
        if close_count < open_count:
            backtrack(current + ')', open_count, close_count + 1)
    
    backtrack('', 0, 0)
    return result

def count_valid_parentheses(n):
    """Count valid parentheses (Catalan number)"""
    if n == 0:
        return 1
    
    dp = [0] * (n + 1)
    dp[0] = 1
    
    for i in range(1, n + 1):
        for j in range(i):
            dp[i] += dp[j] * dp[i - 1 - j]
    
    return dp[n]

# Count paths in grid
def count_paths(m, n, obstacles=None):
    """Count paths from top-left to bottom-right"""
    dp = [[1] * n for _ in range(m)]
    
    # Handle obstacles
    if obstacles:
        for r, c in obstacles:
            dp[r][c] = 0
    
    for i in range(m):
        for j in range(n):
            if obstacles and (i, j) in obstacles:
                dp[i][j] = 0
                continue
            
            if i > 0:
                dp[i][j] = dp[i-1][j]
            if j > 0:
                dp[i][j] += dp[i][j-1]
    
    return dp[m-1][n-1]

# Count frequency of elements
def top_k_frequent(nums, k):
    """Find k most frequent elements"""
    count = Counter(nums)
    # Use bucket sort for O(n) solution
    buckets = [[] for _ in range(len(nums) + 1)]
    
    for num, freq in count.items():
        buckets[freq].append(num)
    
    result = []
    for i in range(len(buckets) - 1, -1, -1):
        for num in buckets[i]:
            result.append(num)
            if len(result) == k:
                return result
    
    return result

# Count inversions in array
def count_inversions(nums):
    """Count number of inversions (i < j but nums[i] > nums[j])"""
    def merge_sort_count(arr):
        if len(arr) <= 1:
            return arr, 0
        
        mid = len(arr) // 2
        left, left_inv = merge_sort_count(arr[:mid])
        right, right_inv = merge_sort_count(arr[mid:])
        
        merged = []
        i = j = inv_count = 0
        
        while i < len(left) and j < len(right):
            if left[i] <= right[j]:
                merged.append(left[i])
                i += 1
            else:
                merged.append(right[j])
                inv_count += len(left) - i  # All remaining elements in left are greater
                j += 1
        
        merged.extend(left[i:])
        merged.extend(right[j:])
        
        return merged, left_inv + right_inv + inv_count
    
    _, inversions = merge_sort_count(nums)
    return inversions

# Count distinct subsequences
def num_distinct(s, t):
    """Count distinct subsequences of s that equal t"""
    m, n = len(s), len(t)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # Empty string t can be formed in 1 way from any string s
    for i in range(m + 1):
        dp[i][0] = 1
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            dp[i][j] = dp[i-1][j]  # Don't use s[i-1]
            
            if s[i-1] == t[j-1]:
                dp[i][j] += dp[i-1][j-1]  # Use s[i-1]
    
    return dp[m][n]

Pro Tips:

  • Use hash maps for frequency counting and prefix sums
  • Dynamic programming often helps with counting problems
  • Consider mathematical formulas (Catalan numbers, combinations)
  • Bucket sort can be useful for frequency-based counting
  • Break complex counting into smaller, manageable parts

🎯 Enhanced Summary & Mastery Guide (Tier 2)

Core Techniques & Complexity Analysis

Topic Time Complexity Space Complexity Key Techniques Must-Know Patterns
Sorting O(n log n) O(1) - O(n) Timsort, Custom comparators Interval merging, 3Sum, Meeting rooms
Binary Search O(log n) O(1) Lower/upper bound, Search space Rotated array, Search answer, Peak finding
Greedy O(n log n) O(1) - O(n) Local optimum β†’ Global Activity selection, Jump game, Gas station
Stack/Monotonic O(n) O(n) LIFO, Monotonic property Next greater, Valid parentheses, Histogram
Heap O(log n) insert/delete O(n) Priority queue, K-way merge Top-K, Running median, Task scheduler
Linked List O(n) traversal O(1) operations Two pointers, Dummy head Reverse, Cycle detection, Merge
Math Varies O(1) - O(n) Number theory, Modular arithmetic GCD/LCM, Prime sieve, Combinatorics
Simulation Problem-dependent Problem-dependent State tracking, Game mechanics Matrix ops, Game of life, Robot movement
Counting O(n) - O(nΒ²) O(n) Hash maps, Prefix sums Subarray sums, Frequency, Palindromes

Problem Recognition Patterns

πŸ” When to Use Each Technique:

  • Sorting: When order matters, interval problems, finding duplicates/pairs
  • Binary Search: Monotonic functions, "find first/last", optimization problems
  • Greedy: Optimization with local choices, scheduling, interval problems
  • Stack: Nested structures, nearest greater/smaller, expression parsing
  • Heap: Top-K problems, merge sorted data, scheduling with priorities
  • Linked List: Pointer manipulation, cycle detection, list operations
  • Math: Number properties, modular arithmetic, combinatorial problems
  • Simulation: Game mechanics, step-by-step processes, state machines
  • Counting: Frequency analysis, subsequences, combinatorial counting

Advanced Tips & Tricks

πŸš€ Pro-Level Optimizations:

  1. Sorting: Use key functions creatively, stable sort properties
  2. Binary Search: Template for any monotonic condition, floating-point search
  3. Greedy: Prove greedy choice property, combine with sorting
  4. Stack: Monotonic stack for range queries, stack + hash for O(1) operations
  5. Heap: Two-heap technique, lazy propagation, heapify vs repeated insertion
  6. Linked List: Sentinel nodes, runner technique, in-place operations
  7. Math: Sieve for multiple queries, extended Euclidean, fast exponentiation
  8. Simulation: State machines, efficient data structures, boundary handling
  9. Counting: Inclusion-exclusion, generating functions, DP + counting

Common Pitfalls & How to Avoid Them

❌ Avoid These Mistakes:

  • Sorting: Forgetting stability requirements, wrong comparator logic
  • Binary Search: Off-by-one errors, infinite loops, wrong boundary conditions
  • Greedy: Not proving optimality, missing counter-examples
  • Stack: Not handling empty stack, wrong monotonic property
  • Heap: Confusing min/max heap, not maintaining heap property
  • Linked List: Null pointer issues, losing references, cycle in modifications
  • Math: Integer overflow, division by zero, negative modulo
  • Simulation: Off-by-one in coordinates, missing edge cases
  • Counting: Double counting, missing base cases, overflow in large numbers

Mastery Checklist

βœ… You've Mastered Tier 2 When You Can:

  • Implement any sorting algorithm and use custom comparators fluently
  • Write binary search for any monotonic condition without bugs
  • Identify and prove when greedy approach works
  • Use monotonic stack to solve range query problems efficiently
  • Implement heap-based algorithms (merge K lists, running median)
  • Manipulate linked lists with two-pointers and dummy nodes
  • Apply number theory concepts (GCD, primes, modular arithmetic)
  • Simulate complex state machines and game mechanics accurately
  • Count complex combinatorial structures using DP and math

🎯 Practice Recommendations:

  • Easy: 50+ problems per topic
  • Medium: 30+ problems per topic
  • Hard: 10+ problems per topic
  • Focus: Pattern recognition and template application

🐍 Python Built-in Library Power-Ups for Tier 2

Advanced Library Integration for Problem-Solving Core

# Enhanced imports for Tier 2 algorithms
from collections import Counter, defaultdict, deque, OrderedDict
from itertools import *
from functools import reduce, lru_cache, partial, cmp_to_key
from operator import add, mul, or_, and_, xor, itemgetter, attrgetter
import heapq
import bisect
import math
import random

# πŸ”₯ SORTING WITH BUILT-IN POWER
def advanced_sorting_techniques():
    """Leveraging Python's sorting capabilities"""
    
    # Multi-key sorting with complex logic
    def sort_students(students):
        # Sort by: grade (desc), then name (asc), then age (asc)
        return sorted(students, key=lambda x: (-x[1], x[0], x[2]))
    
    # Using itemgetter for cleaner multi-field sorting
    from operator import itemgetter
    def sort_with_itemgetter(data):
        # Sort by 2nd field, then 1st field
        return sorted(data, key=itemgetter(1, 0))
    
    # Custom comparator using cmp_to_key
    def largest_number_comparator(x, y):
        # For largest number problem: compare x+y vs y+x
        if x + y > y + x:
            return -1
        elif x + y < y + x:
            return 1
        return 0
    
    def largest_number(nums):
        nums_str = list(map(str, nums))
        nums_str.sort(key=cmp_to_key(largest_number_comparator))
        result = ''.join(nums_str)
        return '0' if result[0] == '0' else result
    
    # Bucket sort using defaultdict
    def bucket_sort_optimized(arr, bucket_size=5):
        if not arr:
            return arr
        
        min_val, max_val = min(arr), max(arr)
        bucket_count = (max_val - min_val) // bucket_size + 1
        buckets = defaultdict(list)
        
        # Distribute into buckets
        for num in arr:
            bucket_idx = (num - min_val) // bucket_size
            buckets[bucket_idx].append(num)
        
        # Sort each bucket and concatenate
        result = []
        for i in range(bucket_count):
            if i in buckets:
                buckets[i].sort()
                result.extend(buckets[i])
        
        return result
    
    # Counting sort with Counter
    def counting_sort_enhanced(arr):
        counter = Counter(arr)
        min_val, max_val = min(counter), max(counter)
        
        result = []
        for val in range(min_val, max_val + 1):
            result.extend([val] * counter[val])
        
        return result

# πŸ”₯ BINARY SEARCH ENHANCED
class BinarySearchToolkit:
    """Advanced binary search patterns using bisect"""
    
    @staticmethod
    def search_insert_position(arr, target):
        """Find position to insert target to maintain sorted order"""
        return bisect.bisect_left(arr, target)
    
    @staticmethod
    def find_first_last_position(arr, target):
        """Find first and last position of target"""
        left = bisect.bisect_left(arr, target)
        if left == len(arr) or arr[left] != target:
            return [-1, -1]
        right = bisect.bisect_right(arr, target) - 1
        return [left, right]
    
    @staticmethod
    def search_2d_matrix_optimized(matrix, target):
        """Search in row-wise and column-wise sorted matrix"""
        if not matrix or not matrix[0]:
            return False
        
        # Flatten conceptually and use binary search
        rows, cols = len(matrix), len(matrix[0])
        
        def get_value(idx):
            return matrix[idx // cols][idx % cols]
        
        left, right = 0, rows * cols - 1
        while left <= right:
            mid = (left + right) // 2
            mid_val = get_value(mid)
            if mid_val == target:
                return True
            elif mid_val < target:
                left = mid + 1
            else:
                right = mid - 1
        
        return False
    
    @staticmethod
    def kth_smallest_in_matrix(matrix, k):
        """Find kth smallest element in sorted matrix using binary search"""
        n = len(matrix)
        left, right = matrix[0][0], matrix[n-1][n-1]
        
        def count_less_equal(mid):
            count = 0
            row, col = n - 1, 0
            while row >= 0 and col < n:
                if matrix[row][col] <= mid:
                    count += row + 1
                    col += 1
                else:
                    row -= 1
            return count
        
        while left < right:
            mid = (left + right) // 2
            if count_less_equal(mid) < k:
                left = mid + 1
            else:
                right = mid
        
        return left

# πŸ”₯ GREEDY ALGORITHMS ENHANCED
def greedy_with_heapq():
    """Greedy algorithms leveraging heapq for efficiency"""
    
    def meeting_rooms_ii_optimized(intervals):
        """Minimum meeting rooms using heap"""
        if not intervals:
            return 0
        
        intervals.sort(key=lambda x: x[0])  # Sort by start time
        heap = []  # End times of ongoing meetings
        
        for start, end in intervals:
            # If earliest ending meeting ends before current starts
            if heap and heap[0] <= start:
                heapq.heappop(heap)
            heapq.heappush(heap, end)
        
        return len(heap)
    
    def task_scheduler_optimized(tasks, n):
        """Task scheduler using Counter and heapq"""
        counter = Counter(tasks)
        max_heap = [-count for count in counter.values()]
        heapq.heapify(max_heap)
        
        time = 0
        while max_heap:
            temp = []
            for _ in range(n + 1):
                if max_heap:
                    temp.append(heapq.heappop(max_heap))
                
            # Add back tasks that still need to be done
            for count in temp:
                if count < -1:  # Still has remaining tasks
                    heapq.heappush(max_heap, count + 1)
            
            # Add time: either full cycle or remaining tasks
            time += (n + 1) if max_heap else len(temp)
        
        return time
    
    def reorganize_string_optimized(s):
        """Reorganize string using Counter and heapq"""
        counter = Counter(s)
        max_heap = [(-count, char) for char, count in counter.items()]
        heapq.heapify(max_heap)
        
        result = []
        prev_count, prev_char = 0, ''
        
        while max_heap:
            count, char = heapq.heappop(max_heap)
            result.append(char)
            
            # Add back previous character if it still has count
            if prev_count < 0:
                heapq.heappush(max_heap, (prev_count, prev_char))
        
            prev_count, prev_char = count + 1, char
        
        return ''.join(result) if len(result) == len(s) else ""

# πŸ”₯ LINKED LIST WITH ENHANCED PATTERNS
def linked_list_advanced():
    """Advanced linked list patterns using built-in optimizations"""
    
    def merge_k_sorted_lists_heap(lists):
        """Merge k sorted lists using heapq"""
        import heapq
        
        heap = []
        # Add first node of each list to heap
        for i, lst in enumerate(lists):
            if lst:
                heapq.heappush(heap, (lst.val, i, lst))
        
        dummy = ListNode(0)
        current = dummy
        
        while heap:
            val, i, node = heapq.heappop(heap)
            current.next = node
            current = current.next
            
            if node.next:
                heapq.heappush(heap, (node.next.val, i, node.next))
        
        return dummy.next
    
    def remove_duplicates_optimized(head):
        """Remove duplicates using set for O(n) time"""
        if not head:
            return head
        
        seen = set()
        dummy = ListNode(0)
        dummy.next = head
        prev = dummy
        current = head
        
        while current:
            if current.val in seen:
                prev.next = current.next
            else:
                seen.add(current.val)
                prev = current
            current = current.next
        
        return dummy.next

# πŸ”₯ NUMBER THEORY ENHANCED
def number_theory_optimized():
    """Number theory using Python's math module and optimizations"""
    
    def gcd_multiple(numbers):
        """GCD of multiple numbers using reduce"""
        return reduce(math.gcd, numbers)
    
    def lcm_multiple(numbers):
        """LCM of multiple numbers"""
        def lcm(a, b):
            return abs(a * b) // math.gcd(a, b)
        return reduce(lcm, numbers)
    
    def sieve_of_eratosthenes_optimized(n):
        """Optimized sieve using list comprehension"""
        sieve = [True] * (n + 1)
        sieve[0] = sieve[1] = False
        
        for i in range(2, int(n**0.5) + 1):
            if sieve[i]:
                # Mark multiples starting from i*i
                for j in range(i*i, n + 1, i):
                    sieve[j] = False
        
        return [i for i, is_prime in enumerate(sieve) if is_prime]
    
    def prime_factorization_optimized(n):
        """Prime factorization using Counter"""
        factors = Counter()
        d = 2
        while d * d <= n:
            while n % d == 0:
                factors[d] += 1
                n //= d
            d += 1
        if n > 1:
            factors[n] += 1
        return factors
    
    def modular_exponentiation(base, exp, mod):
        """Fast modular exponentiation using pow"""
        return pow(base, exp, mod)
    
    def chinese_remainder_theorem(remainders, moduli):
        """CRT using math operations"""
        total = 0
        prod = reduce(mul, moduli)
        
        for r, m in zip(remainders, moduli):
            p = prod // m
            total += r * pow(p, -1, m) * p
        
        return total % prod

# πŸ”₯ COMBINATORICS WITH MATH MODULE
def combinatorics_enhanced():
    """Advanced combinatorics using math.comb and itertools"""
    
    def pascal_triangle_row(n):
        """Generate nth row of Pascal's triangle efficiently"""
        return [math.comb(n, k) for k in range(n + 1)]
    
    def catalan_number(n):
        """Calculate nth Catalan number"""
        return math.comb(2 * n, n) // (n + 1)
    
    def stirling_second_kind(n, k):
        """Stirling numbers of second kind using dynamic programming"""
        if n == 0 and k == 0:
            return 1
        if n == 0 or k == 0:
            return 0
        
        # DP approach with memoization
        @lru_cache(maxsize=None)
        def dp(n, k):
            if k == 1 or k == n:
                return 1
            return k * dp(n-1, k) + dp(n-1, k-1)
        
        return dp(n, k)
    
    def derangements(n):
        """Count derangements (permutations with no fixed points)"""
        if n <= 1:
            return 0 if n == 1 else 1
        
        return (n - 1) * (derangements(n - 1) + derangements(n - 2))
    
    def partition_count(n, k):
        """Count ways to partition n into k parts"""
        @lru_cache(maxsize=None)
        def dp(n, k, min_val=1):
            if k == 1:
                return 1 if n >= min_val else 0
            if n < k * min_val:
                return 0
            
            count = 0
            for i in range(min_val, n - k + 2):
                count += dp(n - i, k - 1, i)
            return count
        
        return dp(n, k)

# πŸ”₯ SIMULATION WITH RANDOM MODULE
def simulation_techniques():
    """Enhanced simulation using random module"""
    
    def monte_carlo_pi(iterations):
        """Estimate Ο€ using Monte Carlo method"""
        inside_circle = 0
        for _ in range(iterations):
            x, y = random.random(), random.random()
            if x*x + y*y <= 1:
                inside_circle += 1
        return 4 * inside_circle / iterations
    
    def reservoir_sampling(stream, k):
        """Reservoir sampling for random k elements from stream"""
        reservoir = []
        
        for i, item in enumerate(stream):
            if i < k:
                reservoir.append(item)
            else:
                # Replace with probability k/(i+1)
                j = random.randint(0, i)
                if j < k:
                    reservoir[j] = item
        
        return reservoir
    
    def shuffle_array(arr):
        """Fisher-Yates shuffle"""
        result = arr.copy()
        for i in range(len(result) - 1, 0, -1):
            j = random.randint(0, i)
            result[i], result[j] = result[j], result[i]
        return result
    
    def weighted_random_selection(weights):
        """Select index with probability proportional to weights"""
        total = sum(weights)
        r = random.uniform(0, total)
        
        cumulative = 0
        for i, weight in enumerate(weights):
            cumulative += weight
            if r <= cumulative:
                return i
        return len(weights) - 1  # Fallback

# πŸ”₯ GAME THEORY AND STATE MACHINES
def game_theory_enhanced():
    """Game theory problems with optimized state management"""
    
    def nim_game_winning_positions(piles):
        """Determine winning positions in Nim game"""
        # XOR all pile sizes (Sprague-Grundy theorem)
        nimber = reduce(xor, piles)
        return nimber != 0  # Non-zero nimber means winning position
    
    def stone_game_dp(piles):
        """Stone game using memoization"""
        @lru_cache(maxsize=None)
        def dp(i, j):
            if i > j:
                return 0
            
            # Current player takes from left or right
            take_left = piles[i] - dp(i + 1, j)
            take_right = piles[j] - dp(i, j - 1)
            
            return max(take_left, take_right)
        
        return dp(0, len(piles) - 1) > 0
    
    def optimal_strategy_for_game(arr):
        """Optimal strategy using 2D DP with memoization"""
        n = len(arr)
        
        @lru_cache(maxsize=None)
        def solve(i, j):
            if i > j:
                return 0
            if i == j:
                return arr[i]
            
            # Take left: arr[i] + min of opponent's best moves
            take_left = arr[i] + min(solve(i+2, j), solve(i+1, j-1))
            # Take right: arr[j] + min of opponent's best moves  
            take_right = arr[j] + min(solve(i+1, j-1), solve(i, j-2))
            
            return max(take_left, take_right)
        
        total = sum(arr)
        player1_max = solve(0, n-1)
        return player1_max > total - player1_max

🎯 Tier 2 Performance Optimizations

Algorithm Standard Approach Built-in Enhanced Performance Gain
Sorting Manual quicksort O(n log n) sorted() with key functions 2-3x faster, more readable
Binary Search Manual implementation bisect module Built-in C optimization
Heap Operations Manual heap maintenance heapq module Optimized C implementation
GCD/LCM Euclidean algorithm math.gcd(), reduce() Faster + handles edge cases
Combinatorics Manual calculation math.comb(), itertools Prevents overflow, optimized
Random Operations Basic random random module methods More distributions available

πŸš€ Interview Power Moves

# Impress with one-liners
top_k_frequent = lambda arr, k: heapq.nlargest(k, Counter(arr).items(), key=itemgetter(1))

merge_intervals = lambda intervals: reduce(lambda acc, curr: acc[:-1] + [acc[-1][:1] + [max(acc[-1][1], curr[1])]] if acc and acc[-1][1] >= curr[0] else acc + [curr], sorted(intervals))

find_kth_largest = lambda arr, k: heapq.nlargest(k, arr)[-1]

# Show deep understanding
def explain_timsort_advantage():
    """Explain why Python's sort is optimal"""
    return "Timsort is hybrid stable sort, optimized for real-world data with existing order"

def demonstrate_bisect_power():
    """Show bisect superiority over manual binary search"""
    import bisect
    # Handles edge cases, cleaner code, C-optimized
    return "bisect handles all edge cases and is implemented in C"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment