Jc-alt logo
jc
data structures and algorithms

LeetCode: Math and Geometry

LeetCode: Math and Geometry
24 min read
#data structures and algorithms
Table Of Contents

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

48. Rotate Image ::1:: - Medium

Topics: Array, Math, Matrix

Intro

You are given an n x n 2D matrix representing an image, rotate the image by 90 degrees (clockwise). You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

Example InputOutput
matrix = [[1,2,3],[4,5,6],[7,8,9]][[7,4,1],[8,5,2],[9,6,3]]
matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]][[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

Constraints:

n == matrix.length == matrix[i].length

1 ≤ n ≤ 20

-1000 ≤ matrix[i][j] ≤ 1000

Abstraction

Given an 2D array, rotate the 2D array 90 degrees.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: Layer by Layer Rotation - Math and Geometry/Math and Geometry

    def rotate(self, matrix: List[List[int]]) -> None:
        
        # Note:
        # Rotate n x n matrix 90 degrees clockwise in place
        # 1. Process the matrix layer by layer (outermost to innermost)
        # 2. For each layer, rotate elements in four way swaps:
        #       Top -> Right -> Bottom -> Left -> Top
        # 3. Repeat for all layers
        # Result -> No extra matrix allocation, in place modification

        # Example (n=3, outer layer rotation):
        # [1, 2, 3]         [7, 4, 1]
        # [4, 5, 6]   --->  [8, 5, 2]
        # [7, 8, 9]         [9, 6, 3]

        n = len(matrix)

        # There are n//2 layers
        # Odd sized matrix -> one single center cell, inner does not rotate
        # Even sized matrix -> no single center cell, all layers rotate
        layers = n//2

        for layer in range(layers):

            # first -> start index of current ring (top-left corner)
            first = layer

            # last -> end index of current ring (bottom-right corner)

            # layer 1:
            # [ 1, 1, 1, 1, 1, 1]
            # [ 1, 1, 1, 1, 1, 1]
            #     f^.       l^
            # ... 
            # ...
            last = n - 1 - layer

            # length of current ring - 1
            # notice we only need 3 swaps as 4th swap is 1 swap of next switch
            # [1,  2,  3,  4 ]
            # [5,  6,  7,  8 ]
            # [9,  10, 11, 12]
            # [13, 14, 15, 16]
            
            # we only swap, 1, 2, 3, as 4 will be taken care of 
            # by the next iteration, therefore ringLen is -1 its actual length
            # thus ring len of 4 goes to 3
            ringLen = last - first            

            # iterate -> 0 to ringLen-1 
            for i in range(ringLen):

                # left -> top
                # bottom -> left
                # right -> bottom
                # saved top -> right

                # save top
                top_val = matrix[first][first + i]

                # top: left right via row    || left: bottom up via column
                matrix[first][first + i] = matrix[last - i][first]

                # left: bottom up via column ||  bottom: right left via row
                matrix[last - i][first] = matrix[last][last - i]

                # bottom: right left via row ||  right: top down via column
                matrix[last][last - i] = matrix[first + i][last]

                # right: top down via column ||  top: left right via row
                matrix[first + i][last] = top_val

        # overall: time complexity O(n^2)
        # overall: space complexity O(1)

54. Spiral Matrix ::1:: - Medium

Topics: Array, Matrix, Simulation

Intro

Given an m x n matrix, return all elements of the matrix in spiral order.

Example InputOutput
matrix = [[1,2,3],[4,5,6],[7,8,9]][1,2,3,6,9,8,7,4,5]
matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]][1,2,3,4,8,12,11,10,9,5,6,7]

Constraints:

m == matrix.length

n == matrix[i].length

1 ≤ m, n ≤ 10

-100 ≤ matrix[i][j] ≤ 100

Abstraction

Given a 2D array, return a list of all the elements in the matrix by iterating in a clockwise rotation.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: Layer by Layer Spiral Traversal - Math and Geometry/Math and Geometry

    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        
        # Note:
        # Traverse the matrix in clockwise spiral order using layers
        # 1. Treat each layer as a shrinking “frame” around the matrix
        # 2. For each layer, traverse top row, right column, bottom row, left column
        # 3. Increment layer and repeat until all elements are processed

        # Empty check
        if not matrix or not matrix[0]:
            return []

        res = []

        # boundaries
        m, n = len(matrix), len(matrix[0])

        # number of layer -> min between rows and columns
        # There are (min(row, col) // 2) layers
        # Odd sized matrix -> one single center cell
        # Even sized matrix -> no single center cell
        layers = (min(m, n) + 1) // 2

        for layer in range(layers):

            # boundaries of current layer
            first_row = layer
            first_col = layer

            # inclusive via last element

            # layer 1:
            # [ 1, 1, 1, 1, 1, 1]
            # [ 1, 1, 1, 1, 1, 1]
            #     f^.       l^
            # ... 
            # ...
            last_row = m - 1 - layer
            last_col = n - 1 - layer

            # Top and Right sides always exist in every layer
            # Bottom and left may not always exist

            # Top row: left -> right
            for col in range(first_col, last_col + 1):
                res.append(matrix[first_row][col])

            # Right column: top+1 -> bottom
            for row in range(first_row + 1, last_row + 1):
                res.append(matrix[row][last_col])

            # Bottom row: right-1 -> left (if more than one row)
            if last_row > first_row:
                for col in range(last_col - 1, first_col - 1, -1):
                    res.append(matrix[last_row][col])

            # Left column: bottom-1 -> top+1 (if more than one column)
            if last_col > first_col:
                for row in range(last_row - 1, first_row, -1):
                    res.append(matrix[row][first_col])

        # overall: time complexity O(m*n)
        # overall: space complexity O(m*n) for output list
        return res

73. Set Matrix Zeroes ::1:: - Medium

Topics: Array, Hash Table, Matrix

Intro

Given an m x n integer matrix matrix, if an element is 0, set its entire row and column to 0's. You must do it in place. Follow up: A straightforward solution using O(mn) space is probably a bad idea. A simple improvement uses O(m + n) space, but still not the best solution. Could you devise a constant space solution?

Example InputOutput
matrix = [[1,1,1],[1,0,1],[1,1,1]][[1,0,1],[0,0,0],[1,0,1]]
matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]][[0,0,0,0],[0,4,5,0],[0,3,1,0]]

Constraints:

m == matrix.length

n == matrix[i].length

1 ≤ m, n ≤ 200

-231 ≤ matrix[i][j] ≤ 231 - 1

Abstraction

Given a 2D array, if a cell has 0, zero out its adjacent row and column.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

Solution 1: Brute Force O(m*n) Space - Math and Geometry/Math and Geometry

    def setZeroes(self, matrix: List[List[int]]) -> None:
        
        # Note:
        # Separate iterating from writing via creating copy of matrix using O(m*n)
        # We read over the copy while updating the original in place
        # 1. Iterate through original matrix
        # 2. If cell is 0, set corresponding row and column in copy to 0
        # 3. Copy back to original matrix
        # Result -> original matrix set correctly in place

        m, n = len(matrix), len(matrix[0])
        
        # Create a copy
        copy = [row[:] for row in matrix]  # O(m*n) space

        for i in range(m):
            for j in range(n):
                if copy[i][j] == 0:
                    # Zero entire row
                    for col in range(n):
                        matrix[i][col] = 0
                    # Zero entire column
                    for row in range(m):
                        matrix[row][j] = 0

        # overall: time complexity O(m*n*(m+n)) due to nested zeroing loops
        # overall: space complexity O(m*n) for copy

Solution 2: Using O(m+n) Space - Math and Geometry/Math and Geometry

    def setZeroes(self, matrix: List[List[int]]) -> None:
        
        # Note:
        # Separate iterating from writing via creating copy of matrix using O(m+n)
        # Use two arrays to store flags to track zero rows and zero columns
        # 1. row_flag[m] -> True if row i should be zero
        # 2. col_flag[n] -> True if column j should be zero
        # 3. Iterate matrix, set flags
        # 4. Iterate matrix again, zero out flagged rows and columns
        # Result -> original matrix set correctly in place


        m, n = len(matrix), len(matrix[0])
        row_flag = [False] * m
        col_flag = [False] * n

        # Flag zero rows and columns
        for i in range(m):
            for j in range(n):
                if matrix[i][j] == 0:
                    row_flag[i] = True
                    col_flag[j] = True

        # Zero out flagged rows and columns in original matrix
        for i in range(m):
            for j in range(n):
                if row_flag[i] or col_flag[j]:
                    matrix[i][j] = 0

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

Solution 3: Constant Space O(1) - Math and Geometry/Math and Geometry

    def setZeroes(self, matrix: List[List[int]]) -> None:
        
        # Note:
        # Separate iterating from writing via creating copy of matrix using O(m+n)
        # We use the first row and first column of the matrix itself
        # 1. row_flag[m] -> True if row i should be zero
        # 2. col_flag[n] -> True if column j should be zero
        # 4. Iterate matrix again, zero out flagged rows and columns
        # Result -> original matrix set correctly in place

        # Boundaries
        m, n = len(matrix), len(matrix[0])

        # Pre check if first row has any zeros
        first_row_zero = False
        for j in range(n):
            if matrix[0][j] == 0:
                first_row_zero = True
                break

        # Pre check if first column has any zeros
        first_col_zero = False
        for i in range(m):
            if matrix[i][0] == 0:
                first_col_zero = True
                break

        # Set zero flags using first row and column
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][j] == 0:
                    matrix[i][0] = 0  # mark row
                    matrix[0][j] = 0  # mark column

        # Using zero flags, zero out rows and cols
        for i in range(1, m):
            for j in range(1, n):
                if matrix[i][0] == 0 or matrix[0][j] == 0:
                    matrix[i][j] = 0

        # Afterwards:
        # Zero out the first row if needed
        if first_row_zero:
            for j in range(n):
                matrix[0][j] = 0

        # Afterwards:
        # Zero out the first column if needed
        if first_col_zero:
            for i in range(m):
                matrix[i][0] = 0

        # overall: time complexity O(m*n)
        # overall: space complexity O(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.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

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.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

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).

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

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.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

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.

Space & Time Complexity

SolutionTime ComplexitySpace ComplexityTime RemarkSpace Remark
BugError

Brute Force:

AspectTime ComplexitySpace ComplexityTime RemarksSpace Remarks

Find the Bug:

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