Jc-alt logo
jc

LeetCode: Math and Geometry

LeetCode: Math and Geometry
11 min read
data structures and algorithms

Math and Geometry Intro

What is Math and Geometry

math!

Math and Geometry IRL

math!

Math and Geometry Application: Math and Geometry

Pattern: math!

Ex: bits numbers!!

    def math!(n: int) -> int:

        return n+1

202. Happy Number ::1:: - Easy

Topics: Hash Table, Math, Two Pointers

Intro

Write an algorithm to determine if a number n is happy. A happy number is a number defined by the following process: Starting with any positive integer, replace the number by the sum of the squares of its digits. Repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1. Those numbers for which this process ends in 1 are happy. Return true if n is a happy number, and false if not.

Example InputOutput
n = 19true
n = 2false

Constraints:

1 ≤ n ≤ 231 - 1

Abstraction

Given a number and constraints, determine if number fits constraints.

Pseudocode

  text will go here

Solution 1: Hash Set Detection - Math and Geometry/Math and Geometry

    def isHappy(self, n: int) -> bool:
        
        # Note:
        # 1. Compute sum of squares of digits repeatedly
        # 2. Keep track of numbers seen in a hash set
        # 3. If we see a number again, a cycle exists → not happy
        # 4. If we reach 1, number is happy
        # Result -> Determine if number is happy

        # store numbers to detect cycles
        seen = set()

        # cycle formula
        def next_number(num):
            return sum(int(d) ** 2 for d in str(num))

        while n != 1:

            # cycle detected
            if n in seen:
                return False
            seen.add(n)

            # compute sum of squares of digits
            n = next_number(n)

        # reached 1 = happy number
        res = True

        # overall: time complexity O(log n) (per iteration * number of unique sums)
        # overall: space complexity O(log n) (for hash set)
        return res

Solution 2: Slow Fast Pointer - Math and Geometry/Math and Geometry

    def isHappy(self, n: int) -> bool:
        
        # Note:
        # Treat sum-of-squares transformation as a linked list
        # 2. Slow and fast pointers to detect cycle
        # 3. If fast or fast.next reaches 1 -> happy
        # 4. If slow == fast → cycle exists -> not happy
        # Result -> Determine if number is happy

        # cycle formula
        def next_number(num):
            return sum(int(d) ** 2 for d in str(num))

        # set pointers
        slow = n
        fast = next_number(n)

        # slow and fast pointer to check happy number requirements
        while fast != 1 and slow != fast:
            slow = next_number(slow)
            fast = next_number(next_number(fast))

        # happy number == 1
        res = fast == 1

        # overall: time complexity O(log n) (per iteration * number of iterations before cycle)
        # overall: space complexity O(1)
        return res

66. Plus One ::1:: - Easy

Topics: Array, Math

Intro

You are given a large integer represented as an integer array digits, where each digits[i] is the ith digit of the integer. The digits are ordered from most significant to least significant in left-to-right order. The large integer does not contain any leading 0's. Increment the large integer by one and return the resulting array of digits.

Example InputOutput
digits = [1,2,3][1,2,4]
digits = [4,3,2,1][4,3,2,2]
digits = [9][1,0]

Constraints:

1 ≤ digits.length ≤ 100

0 ≤ digits[i] ≤ 9

digits does not contain any leading 0's.

Abstraction

Given a number represented as an array of digits (most significant digit first), increment the number by 1 and return the resulting array of digits.

Pseudocode

  text will go here

Solution 1: Hash Set Detection - Math and Geometry/Math and Geometry

    def plusOne(self, digits: List[int]) -> List[int]:
        
        # Note:
        # Process digits from least significant to most significant
        # 1. Iterate right to left in array
        # 2. Add 1 to the last digit
        # 3. Propagate carry if sum >= 10
        # 4. If carry remains after the most significant digit, insert at front
        # 5. Time complexity O(n), space O(1) extra (besides output)

        n = len(digits)

        # iterate from last digit to first
        for i in range(n - 1, -1, -1):
            
            # add to current digit
            digits[i] += 1
            
            # if no carry, finished array, else continue
            if digits[i] < 10:
                return digits

            # if carry, set current to zero, continue
            digits[i] = 0

        # if carry remains after processing all digits
        # prepend [1] to array
        res = [1] + digits

        # overall: time complexity O(n)
        # overall: space complexity O(1)
        return res

50. Pow(x, n) ::1:: - Medium

Topics: Math, Recursion

Intro

Implement pow(x, n), which calculates x raised to the power n (i.e., xn).

Example InputOutput
x = 2.00000, n = 101024.00000
x = 2.10000, n = 39.26100
x = 2.00000, n = -20.25000

Constraints:

-100.0 < x < 100.0

-231 ≤ n ≤ 231 - 1

n is an integer.

Either x is not zero or n > 0.

-104 ≤ xn ≤ 104

Abstraction

Implement pow(x, n).

Pseudocode

  text will go here

Solution 1: Recursive Fast Exponentiation - Math and Geometry/Math and Geometry

    def myPow(self, x: float, n: int) -> float:
        
        # Note:
        # Math behind Fast Exponentiation:

        # x^n -> even -> [x^(n/2)]^2
        #     -> odd  -> x * (x^[(n-1)/2])^2

        # Example:
        # x^6 = (x^3)^2
        # x^7 = x * (x^3)^2

        # Now simply apply some recursion

        # Note:
        # 1. Recursively compute half power to reduce computation
        # 2. If exponent is negative, use reciprocal: x^-n = 1 / x^n
        # 3. Base case: n == 0 → return 1
        # Result -> pow() calculation

        if n == 0:
            return 1.0
        if n < 0:
            return 1 / self.myPow(x, -n)

        half = self.myPow(x, n // 2)
        
        if n % 2 == 0:
            return half * half
        else:
            return half * half * x

        # overall: time complexity O(log n)
        # overall: space complexity O(log n) due to recursion stack

Solution 2: Iterative Fast Binary Exponentiation - Math and Geometry/Math and Geometry

    def myPow(self, x: float, n: int) -> float:
        
        # Note:
        # Binary Exponentiation uses the binary representation of n
        # n = 13 -> 1101

        # Start with result = 1 and current_product = x. 
        # Then for each bit from LSB to MSB:

        # If bit is 1 → multiply result by current_product
        # Square current_product each step
        # Shift to next bit

        # Note:
        # 1. Use binary exponentiation iteratively
        # 2. Convert negative exponent to positive and invert at end
        # 3. Multiply result by x whenever the current bit of n is 1
        # 4. Shift exponent right each iteration

        N = n
        if N < 0:
            x = 1 / x
            N = -N

        result = 1.0
        current_product = x

        while N > 0:
            if N % 2 == 1:  # current bit is 1
                result *= current_product
            current_product *= current_product  # square for next bit
            N //= 2

        # overall: time complexity O(log n)
        # overall: space complexity O(1)
        return result

43. Multiply Strings ::2:: - Medium

Topics: Math, Recursion

Intro

Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and num2, also represented as a string. Note: You must not use any built-in BigInteger library or convert the inputs to integer directly.

Example InputOutput
num1 = "2", num2 = "3""6"
num1 = "123", num2 = "456""56088"

Constraints:

1 ≤ num1.length, num2.length ≤ 200

num1 and num2 consist of digits only.

Both num1 and num2 do not contain any leading zero, except the number 0 itself.

Abstraction

Given two numbers represented as strings, return the product of the two numbers.

Pseudocode

  text will go here

Solution 1: Simulate Grade School Multiplication - Math and Geometry/Math and Geometry

    def multiply(self, num1: str, num2: str) -> str:
        
        # Note:
        # 1. Multiply each digit of num1 by each digit of num2
        # 2. Store intermediate sums in an array of length len(num1) + len(num2)
        # 3. Handle carry for each position
        # 4. Convert array to string, skipping leading zeros

        # Zero Check
        if num1 == "0" or num2 == "0":
            return "0"

        m, n = len(num1), len(num2)

        # max number of digits the product can have when multiplying two integers
        pos = [0] * (m + n)

        # multiply digits from right to left
        for i in range(m - 1, -1, -1):
            for j in range(n - 1, -1, -1):

                # i -> index of digit in num1 
                # j -> index of digit in num2

                # single digit multiplication
                mul = int(num1[i]) * int(num2[j])

                # stores the carry that will affect the next higher digit
                p1 = i + j

                # stores the ones place for this multiplication
                p2 = i + j + 1

                # pos[p2] holds previous carry that contributes to new multiplication
                total = mul + pos[p2]

                # new current digit
                pos[p2] = total % 10

                # new carry
                pos[p1] += total // 10

        # convert to string, skipping leading zeros
        result = []
        
        # to skip leading zeros in final result
        for p in pos:

            # if result == empty, ensures we haven't added any non-zero digits yet
            # which ensures we only skip leading zeros, not inner zeros
            if not result and p == 0:
                continue

            # append inner zeros or values
            result.append(str(p))

        # more efficient of concatenating strings
        res = "".join(result)

        # overall: time complexity O(m*n)
        # overall: space complexity O(m+n)
        return res

2013. Detect Squares ::1:: - Medium

Topics: Array, Hash Table, Design, Counting

Intro

You are given a stream of points on the X-Y plane.
Design an algorithm that: Adds new points from the stream into a data structure. Duplicate points are allowed and should be treated as different points. Given a query point, counts the number of ways such that the three points and the query point form an axis-aligned square with positive area. An axis-aligned square is a square whose edges are all the same length and are either parallel or perpendicular to the x-axis and y-axis. Implement the DetectSquares class: DetectSquares() Initializes the object with an empty data structure. void add(int[] point) Adds a new point point = [x, y] to the data structure. int count(int[] point) Counts the number of ways to form axis-aligned squares with point point = [x, y] as described above.

Example InputOutput
too long[null, null, null, null, 1, 0, null, 2]

Constraints:

point.length == 2

0 ≤ x, y, ≤ 1000

At most 3000 calls in total will be made to add and count.

Abstraction

Design a data structure to store points on a 2D grid and count the number of axis-aligned squares that can be formed with a given query point. Points can be added multiple times. Squares must have edges parallel to axes and positive area. Efficiently handle up to 3000 add and count calls.

Pseudocode

  text will go here

Solution 1: Hash Map Counting - Math and Geometry/Math and Geometry

    class DetectSquares:

        def __init__(self):

            # Note:
            # counts[x][y] -> tracks how many times point (x,y) has been added
            # This allows O(1) insertion and O(k) count queries
            self.counts = defaultdict(lambda: defaultdict(int))

        def add(self, point: List[int]) -> None:

            # Increment point count
            x_col, y_row = point
            self.counts[x_col][y_row] += 1

        def count(self, point: List[int]) -> int:
            x_col, y_row = point
            total = 0

            # Look for all points in same column x, but with different y
            for curr_y_row, count_at_point in self.counts[x_col].items():
                if curr_y_row == y_row:  # skip the query point itself
                    continue

                # distance = side length
                d = curr_y_row - y_row

                # Left square
                left_x = x_col - d
                count_left_top = self.counts.get(left_x, {}).get(y_row, 0)
                count_left_bottom = self.counts.get(left_x, {}).get(curr_y_row, 0)
                total += count_at_point * count_left_top * count_left_bottom

                # Right square
                right_x = x_col + d
                count_right_top = self.counts.get(right_x, {}).get(y_row, 0)
                count_right_bottom = self.counts.get(right_x, {}).get(curr_y_row, 0)
                total += count_at_point * count_right_top * count_right_bottom

            return total

        # overall: add O(1), count O(k) where k = # points sharing same x
        # overall: space complexity O(n) for n points stored