Math Cheatsheet for Algorithms
The math that algorithm problems use, grouped by topic. Each entry gives the formula, what it computes and when you need it, a short Python example with the output of a real run, and the LeetCode problems that use it.
Most geometry problems give integer coordinates. Keep the arithmetic in integers where you can: compare squared distances and use cross products. Then no floating-point rounding can change an answer.
Euclidean distance and squared distance
The Euclidean distance d is the straight-line distance between two points. When you only compare distances, compare d * d instead. Squaring keeps the order of non-negative numbers. The squared distance between two points with integer coordinates is an integer, so it has no rounding error. A point is inside or on a circle of radius r exactly when its squared distance to the centre is at most r * r.
| 1 | def dist_sq(p, q): |
| 2 | return (p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2 |
| 3 | |
| 4 | |
| 5 | def in_circle(point, center, r): |
| 6 | return dist_sq(point, center) <= r * r |
| 7 | |
| 8 | |
| 9 | points = [[3, 3], [5, -1], [-2, 4]] |
| 10 | points.sort(key=lambda p: dist_sq(p, (0, 0))) |
| 11 | print(points) |
| 12 | print(in_circle((3, 4), (0, 0), 5), in_circle((3, 5), (0, 0), 5)) |
[[3, 3], [-2, 4], [5, -1]] True False
LeetCode problems that use it
- K Closest Points to Origin (LC 973): order the points by squared distance to the origin.
- Queries on Number of Points Inside a Circle (LC 1828): count the points with squared distance at most
r * rfor each query circle. - Count Lattice Points Inside a Circle (LC 2249): test every lattice point near each circle with the squared-distance check.
Manhattan distance
The Manhattan distance is the number of steps on a grid when every step moves one cell up, down, left or right. It is the horizontal difference plus the vertical difference. Use it when a problem defines the cost between two points this way.
| 1 | def manhattan(p, q): |
| 2 | return abs(p[0] - q[0]) + abs(p[1] - q[1]) |
| 3 | |
| 4 | |
| 5 | def cells_by_distance(rows, cols, r0, c0): |
| 6 | cells = [[r, c] for r in range(rows) for c in range(cols)] |
| 7 | cells.sort(key=lambda cell: manhattan(cell, (r0, c0))) |
| 8 | return cells |
| 9 | |
| 10 | |
| 11 | print(manhattan((3, 12), (-2, 5))) |
| 12 | print(cells_by_distance(2, 3, 0, 1)) |
12 [[0, 1], [0, 0], [0, 2], [1, 1], [1, 0], [1, 2]]
Chebyshev distance: moves that may go diagonally
Suppose one move changes x by at most 1 and y by at most 1, so a move can be diagonal. Then the fewest moves between two points is the larger of the two differences. Each diagonal move reduces both differences by 1, and straight moves cover what remains of the larger one.
| 1 | def chebyshev(p, q): |
| 2 | return max(abs(p[0] - q[0]), abs(p[1] - q[1])) |
| 3 | |
| 4 | |
| 5 | def time_to_visit(points): |
| 6 | return sum(chebyshev(points[i], points[i + 1]) for i in range(len(points) - 1)) |
| 7 | |
| 8 | |
| 9 | print(chebyshev((0, 0), (2, 5))) |
| 10 | print(time_to_visit([[0, 0], [2, 5], [-1, 1]])) |
5 9
LeetCode problems that use it
- Minimum Time Visiting All Points (LC 1266): the time between two consecutive points is their Chebyshev distance.
Largest Manhattan distance: rotate to u = x + y, v = x − y
The substitution turns every Manhattan distance into a Chebyshev distance between the new points (u, v). So the largest Manhattan distance over all pairs is max(max(u) - min(u), max(v) - min(v)). That takes O(n) time instead of checking all n² pairs. The same idea works with more coordinates. A sum of absolute differences equals the largest signed sum over every choice of + and − signs.
| 1 | def max_manhattan(points): |
| 2 | u = [x + y for x, y in points] |
| 3 | v = [x - y for x, y in points] |
| 4 | return max(max(u) - min(u), max(v) - min(v)) |
| 5 | |
| 6 | |
| 7 | print(max_manhattan([[1, 1], [4, 2], [2, 6], [0, 3]])) |
6
Cross product: which way three points turn
The sign of cross(O, A, B) tells which way the path from O to A to B turns. A positive value is a counter-clockwise (left) turn. A negative value is a clockwise (right) turn. Zero means the three points are on one straight line. With integer coordinates the result is exact, because there is no division.
| 1 | def cross(o, a, b): |
| 2 | return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) |
| 3 | |
| 4 | |
| 5 | print(cross((0, 0), (1, 0), (1, 1))) |
| 6 | print(cross((0, 0), (1, 0), (1, -1))) |
| 7 | print(cross((0, 0), (1, 1), (3, 3))) |
1 -1 0
LeetCode problems that use it
- Check If It Is a Straight Line (LC 1232): every point must give a cross product of 0 with the first two points.
- Valid Boomerang (LC 1037): three points form a boomerang when their cross product is not 0.
- Convex Polygon (LC 469): a polygon is convex when all its nonzero turns have the same sign.
Card that teaches this method: Cross Product
Slope as an exact dictionary key
Two point pairs have the same slope exactly when their reduced direction (dx, dy) is the same. Divide both differences by their gcd. Then fix the sign so that dx > 0, or dx == 0 and dy > 0. The result is a tuple of integers that you can use as a dictionary key, with no floating-point slope.
| 1 | from collections import Counter |
| 2 | from math import gcd |
| 3 | |
| 4 | |
| 5 | def slope_key(p, q): |
| 6 | dx, dy = q[0] - p[0], q[1] - p[1] |
| 7 | g = gcd(dx, dy) |
| 8 | dx, dy = dx // g, dy // g |
| 9 | if dx < 0 or (dx == 0 and dy < 0): |
| 10 | dx, dy = -dx, -dy |
| 11 | return (dx, dy) |
| 12 | |
| 13 | |
| 14 | def max_points(points): |
| 15 | best = 1 |
| 16 | for i, p in enumerate(points): |
| 17 | counts = Counter(slope_key(p, q) for q in points[i + 1 :]) |
| 18 | best = max(best, 1 + max(counts.values(), default=0)) |
| 19 | return best |
| 20 | |
| 21 | |
| 22 | print(slope_key((0, 0), (-4, -6)), slope_key((1, 1), (3, 4))) |
| 23 | print(max_points([[1, 1], [2, 2], [3, 3], [4, 1], [0, 5], [2, 3]])) |
(2, 3) (2, 3) 3
LeetCode problems that use it
- Max Points on a Line (LC 149): for each anchor point, count the other points by slope key.
Card that teaches this method: Canonical Key
Area of a triangle or a polygon: the shoelace formula
List the vertices in order around the polygon, either clockwise or counter-clockwise. The sum is twice the signed area. It is positive for counter-clockwise order and negative for clockwise order. For a triangle the formula is half the absolute cross product of its three corners.
| 1 | def polygon_area(pts): |
| 2 | total = 0 |
| 3 | for i in range(len(pts)): |
| 4 | x1, y1 = pts[i] |
| 5 | x2, y2 = pts[(i + 1) % len(pts)] |
| 6 | total += x1 * y2 - x2 * y1 |
| 7 | return abs(total) / 2 |
| 8 | |
| 9 | |
| 10 | print(polygon_area([(0, 0), (4, 0), (0, 3)])) |
| 11 | print(polygon_area([(0, 0), (4, 0), (4, 4), (2, 6), (0, 4)])) |
6.0 20.0
LeetCode problems that use it
- Largest Triangle Area (LC 812): try every triple of points and take the largest triangle area.
Card that teaches this method: Cross Product
Convex hull with the monotone chain
Sort the points by (x, y). Build the lower chain from left to right, then the upper chain from right to left. Before adding point p, remove the chain's last point while the chain would turn clockwise at it. The test < 0 keeps points that are on an edge of the hull, which Erect the Fence (LC 587) asks for. The test <= 0 keeps only the corners. Sorting makes the whole method O(n log n).
| 1 | def cross(o, a, b): |
| 2 | return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]) |
| 3 | |
| 4 | |
| 5 | def outer_trees(trees): |
| 6 | pts = sorted(map(tuple, trees)) |
| 7 | lower, upper = [], [] |
| 8 | for p in pts: |
| 9 | while len(lower) >= 2 and cross(lower[-2], lower[-1], p) < 0: |
| 10 | lower.pop() |
| 11 | lower.append(p) |
| 12 | for p in reversed(pts): |
| 13 | while len(upper) >= 2 and cross(upper[-2], upper[-1], p) < 0: |
| 14 | upper.pop() |
| 15 | upper.append(p) |
| 16 | return sorted(set(lower + upper)) |
| 17 | |
| 18 | |
| 19 | print(outer_trees([[0, 0], [2, 0], [4, 0], [2, 2], [4, 4], [0, 4], [1, 3]])) |
[(0, 0), (0, 4), (2, 0), (4, 0), (4, 4)]
LeetCode problems that use it
- Erect the Fence (LC 587): return every tree on the fence, including trees in the middle of a straight side.
Card that teaches this method: Cross Product
Overlap of two axis-aligned rectangles
The first rectangle has bottom-left corner (x1, y1) and top-right corner (x2, y2). The second has corners (x3, y3) and (x4, y4). On each axis, the overlap is the smaller end minus the larger start, or 0 when that is negative. The overlap area is w * h. The area the two rectangles cover together is the sum of their areas minus w * h.
| 1 | def overlap(a, b): |
| 2 | w = max(0, min(a[2], b[2]) - max(a[0], b[0])) |
| 3 | h = max(0, min(a[3], b[3]) - max(a[1], b[1])) |
| 4 | return w, h |
| 5 | |
| 6 | |
| 7 | def union_area(a, b): |
| 8 | w, h = overlap(a, b) |
| 9 | area_a = (a[2] - a[0]) * (a[3] - a[1]) |
| 10 | area_b = (b[2] - b[0]) * (b[3] - b[1]) |
| 11 | return area_a + area_b - w * h |
| 12 | |
| 13 | |
| 14 | print(overlap((0, 0, 4, 3), (2, 1, 6, 5))) |
| 15 | print(union_area((0, 0, 4, 3), (2, 1, 6, 5))) |
| 16 | print(overlap((0, 0, 2, 2), (2, 0, 4, 2))) |
(2, 2) 24 (0, 2)
LeetCode problems that use it
- Rectangle Overlap (LC 836): the rectangles overlap only when
w > 0andh > 0, so sharing an edge does not count. - Rectangle Area (LC 223): the covered area is the two areas minus the overlap.
- Find the Largest Area of Square Inside Two Rectangles (LC 3047): the largest square inside an overlap has side
min(w, h).
Does a circle touch a rectangle? Clamp the centre
The point of the rectangle nearest to the centre (xc, yc) is the centre clamped into the rectangle on each axis. The circle and the rectangle share at least one point exactly when that nearest point (px, py) is within distance r of the centre.
| 1 | def circle_touches_rect(r, xc, yc, x1, y1, x2, y2): |
| 2 | px = min(max(xc, x1), x2) |
| 3 | py = min(max(yc, y1), y2) |
| 4 | return (px - xc) ** 2 + (py - yc) ** 2 <= r * r |
| 5 | |
| 6 | |
| 7 | print(circle_touches_rect(2, 0, 0, 1, 1, 3, 3)) |
| 8 | print(circle_touches_rect(1, 0, 0, 1, 1, 3, 3)) |
| 9 | print(circle_touches_rect(1, 2, 2, 0, 0, 5, 5)) |
True False True
LeetCode problems that use it
- Circle and Rectangle Overlapping (LC 1401): clamp the centre into the rectangle and compare squared distances.
Turning a direction by 90 degrees
Store the facing direction as a vector (dx, dy). A 90-degree turn swaps the two parts and negates one of them, so no angle arithmetic is needed. Four turns in the same direction give back the original vector.
| 1 | def is_bounded(instructions): |
| 2 | x, y, dx, dy = 0, 0, 0, 1 |
| 3 | for step in instructions: |
| 4 | if step == "G": |
| 5 | x, y = x + dx, y + dy |
| 6 | elif step == "L": |
| 7 | dx, dy = -dy, dx |
| 8 | else: |
| 9 | dx, dy = dy, -dx |
| 10 | return (x, y) == (0, 0) or (dx, dy) != (0, 1) |
| 11 | |
| 12 | |
| 13 | print(is_bounded("GR"), is_bounded("GGRGGL"), is_bounded("GLGLGLG")) |
True False True
Angle of a point with atan2
math.atan2(dy, dx) returns the direction from (x0, y0) to (x, y) in radians, and it handles every quadrant and dx == 0. Convert it with math.degrees. To count points inside a viewing angle that may cross the 180-degree boundary, sort the angles and append each angle plus 360. Then, for each angle as the start, use bisect_right to count the angles that are at most the viewing angle after it.
| 1 | import math |
| 2 | from bisect import bisect_right |
| 3 | |
| 4 | |
| 5 | def visible_points(points, angle, location): |
| 6 | x0, y0 = location |
| 7 | at_location = 0 |
| 8 | angles = [] |
| 9 | for x, y in points: |
| 10 | if (x, y) == (x0, y0): |
| 11 | at_location += 1 |
| 12 | else: |
| 13 | angles.append(math.degrees(math.atan2(y - y0, x - x0))) |
| 14 | angles.sort() |
| 15 | angles += [a + 360 for a in angles] |
| 16 | best = 0 |
| 17 | for i in range(len(angles) // 2): |
| 18 | best = max(best, bisect_right(angles, angles[i] + angle + 1e-9) - i) |
| 19 | return best + at_location |
| 20 | |
| 21 | |
| 22 | print(round(math.degrees(math.atan2(1, -1)), 6)) |
| 23 | print(visible_points([[1, 0], [0, 1], [-1, 0], [0, -1], [1, 1]], 90, [0, 0])) |
135.0 3
LeetCode problems that use it
- Maximum Number of Visible Points (LC 1610): sort the angles, append each plus 360, and count the angles within
angledegrees after each start angle.
Angle between the hands of a clock
The minute hand moves 6 degrees per minute. The hour hand moves 30 degrees per hour plus 0.5 degrees per minute. Their difference is 30h + 0.5m - 6m = 30h - 5.5m. The smaller angle between the hands is a or 360 - a, whichever is smaller.
| 1 | def clock_angle(hour, minutes): |
| 2 | a = abs(30 * (hour % 12) - 5.5 * minutes) |
| 3 | return min(a, 360 - a) |
| 4 | |
| 5 | |
| 6 | print(clock_angle(6, 0), clock_angle(2, 20), clock_angle(9, 45)) |
180.0 50.0 22.5
LeetCode problems that use it
- Angle Between Hands of a Clock (LC 1344): apply the formula and take the smaller of the two angles.
Do four points form a square? Check the six distances
Four points have six pairwise distances. They form a square exactly when the six squared distances take only two values, a nonzero side value s that appears four times and a diagonal value 2s that appears twice. Squared distances keep the test in integers.
| 1 | from collections import Counter |
| 2 | from itertools import combinations |
| 3 | |
| 4 | |
| 5 | def valid_square(p1, p2, p3, p4): |
| 6 | d = Counter( |
| 7 | (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 |
| 8 | for a, b in combinations([p1, p2, p3, p4], 2) |
| 9 | ) |
| 10 | if len(d) != 2 or 0 in d: |
| 11 | return False |
| 12 | side, diag = sorted(d) |
| 13 | return d[side] == 4 and d[diag] == 2 and diag == 2 * side |
| 14 | |
| 15 | |
| 16 | print(valid_square([0, 0], [2, 1], [1, 3], [-1, 2])) |
| 17 | print(valid_square([0, 0], [2, 0], [2, 1], [0, 1])) |
True False
LeetCode problems that use it
- Valid Square (LC 593): sort or count the six squared distances.
Some problems ask for the value h at which a total reaches a target. When the total never decreases as h grows, binary search on h finds it.
Area of squares below a horizontal line
Square i has its bottom edge at yi and side li. The part of the square below the line y = h has height h - yi, clamped to the range 0 to li. The function A(h) never decreases as h grows. So a binary search on real numbers finds the lowest h where A(h) reaches half of the total area.
| 1 | def area_below(squares, h): |
| 2 | return sum(l * min(l, max(0, h - y)) for _, y, l in squares) |
| 3 | |
| 4 | |
| 5 | def half_area_line(squares): |
| 6 | total = sum(l * l for _, _, l in squares) |
| 7 | lo, hi = 0.0, max(y + l for _, y, l in squares) |
| 8 | for _ in range(100): |
| 9 | mid = (lo + hi) / 2 |
| 10 | if area_below(squares, mid) * 2 >= total: |
| 11 | hi = mid |
| 12 | else: |
| 13 | lo = mid |
| 14 | return hi |
| 15 | |
| 16 | |
| 17 | squares = [[0, 0, 2], [5, 1, 3]] |
| 18 | print(area_below(squares, 2)) |
| 19 | print(round(half_area_line(squares), 5)) |
7 1.9
LeetCode problems that use it
- Separate Squares I (LC 3453): find the lowest
hwhere the area below the line is half of the total area. An area covered by several squares counts once for each of them.
Card that teaches this method: Continuous Bisection
How many rounds a binary search on real numbers needs
Each round halves the interval from lo to hi. For hi - lo = 1e9 and a tolerance eps = 1e-5, you need k = 47 rounds, because . Prefer a fixed number of rounds to the loop while hi - lo > eps. That loop never ends when eps is smaller than the gap between two neighbouring floats near the answer. In that case mid equals lo or hi, and the interval stops shrinking.
| 1 | import math |
| 2 | |
| 3 | lo, hi, eps = 0.0, 1e9, 1e-5 |
| 4 | print(math.ceil(math.log2((hi - lo) / eps))) |
| 5 | |
| 6 | lo, hi = 1e9, 1e9 + 1 |
| 7 | for _ in range(200): |
| 8 | mid = (lo + hi) / 2 |
| 9 | if mid in (lo, hi): |
| 10 | break |
| 11 | lo = mid |
| 12 | print(hi - lo, math.ulp(1e9)) |
47 1.1920928955078125e-07 1.1920928955078125e-07
LeetCode problems that use it
- Separate Squares I (LC 3453): the answer must be within
10^-5, so a fixed count of rounds is enough. - Minimize Max Distance to Gas Station (LC 774): binary-search the smallest possible largest gap
Don real numbers.
Card that teaches this method: Continuous Bisection
Integer square root
math.isqrt(n) returns this value exactly, for an integer of any size. int(math.sqrt(n)) first converts n to a float, and that can round up for large n. It also solves quadratic limits. For example, the largest k with k(k + 1)/2 <= n is (isqrt(8n + 1) - 1) // 2.
| 1 | import math |
| 2 | |
| 3 | n = 10**16 - 1 |
| 4 | print(math.isqrt(n), int(math.sqrt(n))) |
| 5 | |
| 6 | |
| 7 | def arrange_coins(n): |
| 8 | return (math.isqrt(8 * n + 1) - 1) // 2 |
| 9 | |
| 10 | |
| 11 | print(arrange_coins(7), arrange_coins(6), arrange_coins(10)) |
99999999 100000000 3 3 4
LeetCode problems that use it
- Sqrt(x) (LC 69): binary-search the largest
rwithr * r <= x.math.isqrt(x)gives the same value, but the problem expects you to compute it yourself. - Valid Perfect Square (LC 367): binary-search the largest
xwithx * x <= num, then check whetherx * x == num. The problem does not allow a built-in square root such asmath.isqrt. - Arranging Coins (LC 441): the number of complete rows is the largest
kwithk(k + 1)/2 <= n.
Card that teaches this method: Upper Bound
These formulas work on whole numbers: greatest common divisors, primes, remainders and number bases.
Greatest common divisor with Euclid's algorithm
Every common divisor of a and b also divides a mod b, so the pair (b, a mod b) has the same gcd. The second number shrinks quickly, so the loop runs O(log min(a, b)) times. Python has math.gcd, and since Python 3.9 it accepts any number of arguments.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def gcd(a, b): |
| 5 | while b: |
| 6 | a, b = b, a % b |
| 7 | return a |
| 8 | |
| 9 | |
| 10 | print(gcd(84, 36), math.gcd(12, 18, 30)) |
12 6
LeetCode problems that use it
- Find Greatest Common Divisor of Array (LC 1979): the gcd of the smallest and the largest value.
- Greatest Common Divisor of Strings (LC 1071): when
str1 + str2 == str2 + str1, the answer is the prefix of lengthgcd(len(str1), len(str2)). - X of a Kind in a Deck of Cards (LC 914): a valid split exists when the gcd of all the value counts is at least 2.
- Number of Subarrays With GCD Equal to K (LC 2447): extend each subarray to the right while its running gcd is still a multiple of
k. - Simplified Fractions (LC 1447): a fraction
n / dis in lowest terms exactly whengcd(n, d) == 1.
Card that teaches this method: Euclid's GCD
Least common multiple
The lcm is the smallest positive number that both a and b divide. Divide before you multiply, so the intermediate value stays small in languages with fixed-size integers. Python has math.lcm since Python 3.9.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def lcm(a, b): |
| 5 | return a // math.gcd(a, b) * b |
| 6 | |
| 7 | |
| 8 | print(lcm(4, 6), lcm(2, 7), math.lcm(4, 6, 10)) |
12 14 60
Which amounts can two numbers make? Bézout's identity
Every integer combination a * x + b * y is a multiple of gcd(a, b). Every multiple of gcd(a, b) can be written that way. For two jugs, the amounts you can measure are the multiples of the gcd that also fit into both jugs together.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def can_measure(x, y, target): |
| 5 | return target <= x + y and target % math.gcd(x, y) == 0 |
| 6 | |
| 7 | |
| 8 | print(can_measure(4, 6, 8), can_measure(4, 6, 5), can_measure(3, 7, 11)) |
True False False
All primes below n: the sieve of Eratosthenes
A composite number below n has a prime factor at most its square root, so marking stops at p * p >= n. Marking starts at p * p because every smaller multiple of p has a smaller prime factor and is already marked.
| 1 | def primes_below(n): |
| 2 | is_prime = [True] * max(n, 2) |
| 3 | is_prime[0] = is_prime[1] = False |
| 4 | p = 2 |
| 5 | while p * p < n: |
| 6 | if is_prime[p]: |
| 7 | for m in range(p * p, n, p): |
| 8 | is_prime[m] = False |
| 9 | p += 1 |
| 10 | return [i for i in range(n) if is_prime[i]] |
| 11 | |
| 12 | |
| 13 | print(primes_below(30)) |
| 14 | print(len(primes_below(10**6))) |
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29] 78498
LeetCode problems that use it
- Count Primes (LC 204): count the primes strictly less than
n. - Prime Arrangements (LC 1175): with
Pprimes up ton, the answer isP! * (n - P)!modulo109 + 7. - Closest Prime Numbers in Range (LC 2523): sieve up to
right, then compare neighbouring primes in the range.
Card that teaches this method: Sieve of Eratosthenes
Smallest-prime-factor sieve: factor many numbers fast
One sieve pass stores the smallest prime factor spf[x] of every x up to a limit. To factor x, divide by spf[x] until x is 1. Each division at least halves x, so factoring takes O(log x) steps after the sieve.
| 1 | def build_spf(limit): |
| 2 | spf = list(range(limit + 1)) |
| 3 | p = 2 |
| 4 | while p * p <= limit: |
| 5 | if spf[p] == p: |
| 6 | for m in range(p * p, limit + 1, p): |
| 7 | if spf[m] == m: |
| 8 | spf[m] = p |
| 9 | p += 1 |
| 10 | return spf |
| 11 | |
| 12 | |
| 13 | def prime_factors(x, spf): |
| 14 | factors = [] |
| 15 | while x > 1: |
| 16 | factors.append(spf[x]) |
| 17 | x //= spf[x] |
| 18 | return factors |
| 19 | |
| 20 | |
| 21 | spf = build_spf(100) |
| 22 | print(prime_factors(84, spf), prime_factors(97, spf)) |
[2, 2, 3, 7] [97]
Divisors and prime factors by trial division up to √n
Divisors come in pairs (d, n // d), and the smaller one of each pair is at most √n. So a loop over d while d * d <= n finds every divisor in O(√n) time. For prime factors, divide out each d completely. Whatever is left above 1 at the end is one last prime factor.
| 1 | def divisors(n): |
| 2 | small, large = [], [] |
| 3 | d = 1 |
| 4 | while d * d <= n: |
| 5 | if n % d == 0: |
| 6 | small.append(d) |
| 7 | if d != n // d: |
| 8 | large.append(n // d) |
| 9 | d += 1 |
| 10 | return small + large[::-1] |
| 11 | |
| 12 | |
| 13 | def prime_factors(n): |
| 14 | factors = {} |
| 15 | d = 2 |
| 16 | while d * d <= n: |
| 17 | while n % d == 0: |
| 18 | factors[d] = factors.get(d, 0) + 1 |
| 19 | n //= d |
| 20 | d += 1 |
| 21 | if n > 1: |
| 22 | factors[n] = factors.get(n, 0) + 1 |
| 23 | return factors |
| 24 | |
| 25 | |
| 26 | print(divisors(36)) |
| 27 | print(prime_factors(360)) |
[1, 2, 3, 4, 6, 9, 12, 18, 36]
{2: 3, 3: 2, 5: 1}LeetCode problems that use it
- The kth Factor of n (LC 1492): list the divisors in increasing order and return the
k-th one. - Four Divisors (LC 1390): stop a number as soon as it has more than four divisors.
- Distinct Prime Factors of Product of Array (LC 2521): collect the distinct prime factors of every number.
- Smallest Value After Replacing With Sum of Prime Factors (LC 2507): replace
nby the sum of its prime factors until it stops changing.
Only perfect squares have an odd number of divisors
Divisors of n come in pairs (d, n // d). The two members of a pair are different unless d * d == n. So the count is odd exactly when n is a perfect square. Between 1 and n there are isqrt(n) perfect squares.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def bulbs_on(n): |
| 5 | return math.isqrt(n) |
| 6 | |
| 7 | |
| 8 | print([d for d in range(1, 17) if 16 % d == 0]) |
| 9 | print(bulbs_on(10), bulbs_on(100)) |
[1, 2, 4, 8, 16] 3 10
LeetCode problems that use it
- Bulb Switcher (LC 319): bulb
iis toggled once per divisor ofi, so it ends on wheniis a perfect square.
Arithmetic modulo
When a problem asks for the answer modulo M = 109 + 7, reduce after every addition and multiplication. Python integers never overflow, but a very large integer makes each operation slower. Division does not follow these rules. It needs the modular inverse, described in the entry on dividing modulo a prime. In Python, a % M is always in the range 0 to M - 1 for a positive M, even when a is negative. C++ and Java return a negative remainder for a negative a.
| 1 | MOD = 10**9 + 7 |
| 2 | |
| 3 | |
| 4 | def dice_ways(n, k, target): |
| 5 | ways = [1] + [0] * target |
| 6 | for _ in range(n): |
| 7 | new = [0] * (target + 1) |
| 8 | for total in range(target + 1): |
| 9 | if ways[total]: |
| 10 | for face in range(1, k + 1): |
| 11 | if total + face <= target: |
| 12 | new[total + face] = (new[total + face] + ways[total]) % MOD |
| 13 | ways = new |
| 14 | return ways[target] |
| 15 | |
| 16 | |
| 17 | print(-7 % 5, -7 // 5) |
| 18 | print(dice_ways(3, 6, 10), dice_ways(20, 20, 200)) |
3 -2 27 270319085
LeetCode problems that use it
Subarray sums divisible by k: equal prefix remainders
P[j] is the sum of the first j values, with P[0] = 0. A subarray sum is a difference of two prefix sums. It is divisible by k exactly when the two prefix sums have the same remainder. So count equal remainders in a dictionary while you scan the array.
| 1 | from collections import Counter |
| 2 | |
| 3 | |
| 4 | def subarrays_div_by_k(nums, k): |
| 5 | seen = Counter({0: 1}) |
| 6 | prefix = count = 0 |
| 7 | for x in nums: |
| 8 | prefix = (prefix + x) % k |
| 9 | count += seen[prefix] |
| 10 | seen[prefix] += 1 |
| 11 | return count |
| 12 | |
| 13 | |
| 14 | print(subarrays_div_by_k([3, 1, 2, -3, 6], 3)) |
10
LeetCode problems that use it
- Subarray Sums Divisible by K (LC 974): count the pairs of equal prefix remainders.
- Continuous Subarray Sum (LC 523): store the first index of each remainder, and require a length of at least 2.
Card that teaches this method: Prefix Sum Map
Fast power by repeated squaring
Read the bits of n from the lowest. Square the base once per bit, and multiply the result by the base when the bit is 1. That takes O(log n) multiplications instead of n. Python's pow(a, n, m) does the same with a modulus.
| 1 | def power(a, n, mod): |
| 2 | result = 1 |
| 3 | a %= mod |
| 4 | while n: |
| 5 | if n & 1: |
| 6 | result = result * a % mod |
| 7 | a = a * a % mod |
| 8 | n >>= 1 |
| 9 | return result |
| 10 | |
| 11 | |
| 12 | print(power(3, 13, 1000), pow(3, 13, 1000)) |
| 13 | print(power(2, 10**18, 10**9 + 7)) |
323 323 719476260
LeetCode problems that use it
- Pow(x, n) (LC 50): square the base and halve the exponent, and for a negative
nuse1 / xas the base. - Super Pow (LC 372): the exponent is given as a digit list, so combine
a^(10 * e + d) = (a^e)^10 * a^d. - Count Good Numbers (LC 1922): the answer is
5^ceil(n/2) * 4^floor(n/2)modulo109 + 7.
Card that teaches this method: Fast Power
Dividing modulo a prime: the modular inverse
Fermat's little theorem gives a^(M-1) ≡ 1 modulo a prime M, so a^(M-2) is the inverse of a. Multiplying by it replaces division. Since Python 3.8, pow(a, -1, M) computes the inverse directly. Precompute factorials and inverse factorials once, and every binomial coefficient costs O(1).
| 1 | MOD = 10**9 + 7 |
| 2 | N = 1000 |
| 3 | fact = [1] * (N + 1) |
| 4 | for i in range(1, N + 1): |
| 5 | fact[i] = fact[i - 1] * i % MOD |
| 6 | inv_fact = [1] * (N + 1) |
| 7 | inv_fact[N] = pow(fact[N], MOD - 2, MOD) |
| 8 | for i in range(N, 0, -1): |
| 9 | inv_fact[i - 1] = inv_fact[i] * i % MOD |
| 10 | |
| 11 | |
| 12 | def comb_mod(n, k): |
| 13 | if k < 0 or k > n: |
| 14 | return 0 |
| 15 | return fact[n] * inv_fact[k] % MOD * inv_fact[n - k] % MOD |
| 16 | |
| 17 | |
| 18 | print(pow(3, MOD - 2, MOD) * 3 % MOD, pow(3, -1, MOD) == pow(3, MOD - 2, MOD)) |
| 19 | print(comb_mod(10, 3), comb_mod(1000, 500)) |
1 True 120 159835829
LeetCode problems that use it
- Number of Ways to Reach a Position After Exactly k Steps (LC 2400): with distance
d = |endPos - startPos|, the answer isC(k, (k + d) / 2)whenk + dis even andd <= k. - Count Ways to Build Rooms in an Ant Colony (LC 1916): the count is
n!divided by the product of all subtree sizes, so divide with inverses.
Digital root: repeated digit sum
A number and its digit sum have the same remainder modulo 9, because 10 ≡ 1 modulo 9. Summing digits until one digit remains therefore gives the remainder modulo 9, with 9 in place of 0 for a positive n.
| 1 | def digital_root(n): |
| 2 | return 0 if n == 0 else 1 + (n - 1) % 9 |
| 3 | |
| 4 | |
| 5 | print(digital_root(47), digital_root(99999), digital_root(0)) |
2 9 0
LeetCode problems that use it
- Add Digits (LC 258): return the digital root in
O(1)time.
Converting a number to another base
Repeated divmod(n, b) produces the digits from the least significant one, so reverse them at the end. For a negative base such as −2, a remainder can come out negative. Then add |b| to the remainder and add 1 to the quotient. For a negative number in hexadecimal as a 32-bit value, first take n & 0xFFFFFFFF, which is its two's complement.
| 1 | DIGITS = "0123456789abcdef" |
| 2 | |
| 3 | |
| 4 | def to_base(n, b): |
| 5 | if n == 0: |
| 6 | return "0" |
| 7 | sign, n, out = "-" if n < 0 else "", abs(n), [] |
| 8 | while n: |
| 9 | n, d = divmod(n, b) |
| 10 | out.append(DIGITS[d]) |
| 11 | return sign + "".join(reversed(out)) |
| 12 | |
| 13 | |
| 14 | def to_base_neg2(n): |
| 15 | out = [] |
| 16 | while n: |
| 17 | n, d = divmod(n, -2) |
| 18 | if d < 0: |
| 19 | n, d = n + 1, d + 2 |
| 20 | out.append(str(d)) |
| 21 | return "".join(reversed(out)) or "0" |
| 22 | |
| 23 | |
| 24 | print(to_base(50, 7), to_base(-20, 7)) |
| 25 | print(to_base(-2 & 0xFFFFFFFF, 16), to_base(255, 16)) |
| 26 | print(to_base_neg2(6), to_base_neg2(7)) |
101 -26 fffffffe ff 11010 11011
Excel column titles: base 26 with digits 1 to 26
Column titles have no zero digit. A is 1 and Z is 26. So subtract 1 before each divmod(n, 26), and read the remainder as 0 for A up to 25 for Z. For the reverse direction, n = n * 26 + (letter value) for each letter from the left.
| 1 | def column_title(n): |
| 2 | out = [] |
| 3 | while n: |
| 4 | n, r = divmod(n - 1, 26) |
| 5 | out.append(chr(ord("A") + r)) |
| 6 | return "".join(reversed(out)) |
| 7 | |
| 8 | |
| 9 | def column_number(title): |
| 10 | n = 0 |
| 11 | for ch in title: |
| 12 | n = n * 26 + (ord(ch) - ord("A") + 1) |
| 13 | return n |
| 14 | |
| 15 | |
| 16 | print(column_title(26), column_title(27), column_title(702), column_title(703)) |
| 17 | print(column_number("AZ"), column_number("BA")) |
Z AA ZZ AAA 52 53
Trailing zeros of n!
Each trailing zero needs one factor 10, which is 2 × 5. Factors of 2 are more common than factors of 5 in n!. So the count of zeros is the count of factors 5 in n!. Each multiple of 5 gives one factor, each multiple of 25 gives one more, and so on.
| 1 | def trailing_zeros(n): |
| 2 | count = 0 |
| 3 | while n: |
| 4 | n //= 5 |
| 5 | count += n |
| 6 | return count |
| 7 | |
| 8 | |
| 9 | print(trailing_zeros(25), trailing_zeros(100), trailing_zeros(10**9)) |
6 24 249999998
Staying inside the 32-bit integer range
Python integers never overflow. So when a problem describes a 32-bit environment, you must compare with the range yourself. Division is the other difference. Python's // rounds down, while C++, Java and many problem statements truncate toward zero. For truncation, divide the absolute values and apply the sign.
| 1 | INT_MIN, INT_MAX = -(2**31), 2**31 - 1 |
| 2 | |
| 3 | |
| 4 | def clamp32(x): |
| 5 | return max(INT_MIN, min(INT_MAX, x)) |
| 6 | |
| 7 | |
| 8 | def trunc_div(a, b): |
| 9 | q = abs(a) // abs(b) |
| 10 | return q if (a < 0) == (b < 0) else -q |
| 11 | |
| 12 | |
| 13 | print(INT_MIN, INT_MAX) |
| 14 | print(clamp32(2**40), -7 // 2, trunc_div(-7, 2)) |
-2147483648 2147483647 2147483647 -4 -3
LeetCode problems that use it
- Reverse Integer (LC 7): return 0 when the reversed number is outside the range.
- String to Integer (atoi) (LC 8): clamp the parsed value to the range.
- Divide Two Integers (LC 29): truncate toward zero. The only result outside the range is
-231 / -1. The problem does not allow//, so build the quotient from subtractions of shifted divisors.
Card that teaches this method: Digit Manipulation
These formulas count arrangements, selections and pairs directly, so a solution never has to list them one by one.
Binomial coefficient: choose k of n
C(n, k) counts the ways to choose k items from n when order does not matter. Python's math.comb(n, k) computes it exactly. A grid path with r moves down and c moves right is one choice of which r of the r + c moves go down.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def unique_paths(m, n): |
| 5 | return math.comb(m + n - 2, m - 1) |
| 6 | |
| 7 | |
| 8 | print(math.comb(5, 2), unique_paths(4, 5), unique_paths(10, 10)) |
10 35 48620
Pascal's rule: binomial coefficients by addition
Either the n-th item is chosen, and k - 1 more come from the other n - 1, or it is not chosen, and all k come from the other n - 1. The rule builds a table of all C(i, j) with additions only. So it also works under any modulus, prime or not, with no division.
| 1 | def pascal_rows(num_rows): |
| 2 | rows = [[1]] |
| 3 | for _ in range(num_rows - 1): |
| 4 | prev = rows[-1] |
| 5 | rows.append([1] + [prev[j - 1] + prev[j] for j in range(1, len(prev))] + [1]) |
| 6 | return rows |
| 7 | |
| 8 | |
| 9 | print(pascal_rows(6)) |
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1], [1, 5, 10, 10, 5, 1]]
LeetCode problems that use it
- Pascal's Triangle (LC 118): build each row from the previous one.
- Pascal's Triangle II (LC 119): keep only one row and update it from right to left.
- Number of Ways to Reorder Array to Get Same BST (LC 1569): the left and right subtree orders interleave in
C(left + right, left)ways.
Card that teaches this method: Grid DP
Catalan numbers
Cn counts three things of size n. It counts the binary search trees with n keys and the balanced strings of n pairs of parentheses. It also counts the ways to join 2n points on a circle with n chords that do not cross. The sum form fixes the root of the tree, or the position of the ) that matches the first (. Then i items go on one side and n - i on the other. The sequence starts 1, 1, 2, 5, 14, 42.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def catalan(n): |
| 5 | return math.comb(2 * n, n) // (n + 1) |
| 6 | |
| 7 | |
| 8 | def catalan_dp(n): |
| 9 | c = [1] + [0] * n |
| 10 | for m in range(1, n + 1): |
| 11 | c[m] = sum(c[i] * c[m - 1 - i] for i in range(m)) |
| 12 | return c[n] |
| 13 | |
| 14 | |
| 15 | print([catalan(n) for n in range(8)]) |
| 16 | print(catalan_dp(19)) |
[1, 1, 2, 5, 14, 42, 132, 429] 1767263190
LeetCode problems that use it
- Unique Binary Search Trees (LC 96): the number of binary search trees with keys 1 to
nisCn. - Generate Parentheses (LC 22): the formula does not build the strings, but the output has
Cnstrings, so any solution takes at leastCnsteps, which bounds its running time. - Handshakes That Don't Cross (LC 1259): for
numPeoplepeople the answer isC_(numPeople / 2)modulo109 + 7.
Stars and bars: splitting n equal items into k groups
Write the n items as stars and put k - 1 bars between them to separate the k groups. Every arrangement of n stars and k - 1 bars in a row is one split. So the count is the number of ways to place the bars among n + k - 1 positions.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def splits(n, k): |
| 5 | return math.comb(n + k - 1, k - 1) |
| 6 | |
| 7 | |
| 8 | def sorted_vowel_strings(n): |
| 9 | return splits(n, 5) |
| 10 | |
| 11 | |
| 12 | print(splits(4, 3), sorted_vowel_strings(3), sorted_vowel_strings(10)) |
15 35 1001
Arrangements of a multiset
n items where value i appears ci times have this many different orders. The n! orders count each arrangement once for every way of reordering equal values among themselves. Dividing by each ci! removes those repeats.
| 1 | import math |
| 2 | from collections import Counter |
| 3 | from itertools import permutations |
| 4 | |
| 5 | |
| 6 | def arrangements(word): |
| 7 | result = math.factorial(len(word)) |
| 8 | for c in Counter(word).values(): |
| 9 | result //= math.factorial(c) |
| 10 | return result |
| 11 | |
| 12 | |
| 13 | print(arrangements("aabbb"), len(set(permutations("aabbb")))) |
| 14 | print(arrangements("mississippi")) |
10 10 34650
The k-th permutation: the factorial number system
The permutations that start with the same first item form a block of (n - 1)! permutations. So (k - 1) // (n - 1)! is the index of the first item among the unused items. Keep the remainder and repeat with (n - 2)! for the next position.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def kth_permutation(n, k): |
| 5 | items = list(range(1, n + 1)) |
| 6 | k -= 1 |
| 7 | out = [] |
| 8 | for i in range(n, 0, -1): |
| 9 | q, k = divmod(k, math.factorial(i - 1)) |
| 10 | out.append(str(items.pop(q))) |
| 11 | return "".join(out) |
| 12 | |
| 13 | |
| 14 | print(kth_permutation(3, 1), kth_permutation(3, 4), kth_permutation(4, 24)) |
123 231 4321
LeetCode problems that use it
- Permutation Sequence (LC 60): pick each digit with
divmod(k - 1, (n - 1)!).
Card that teaches this method: Count and Skip
Inclusion–exclusion
Adding the sizes of the sets counts every element of an intersection more than once. Subtract the pairwise intersections, then add back the triple intersection. For example, the numbers from 1 to x divisible by a or b number x // a + x // b - x // lcm(a, b). With an upper limit per group, stars and bars plus inclusion–exclusion subtracts the splits where some group is over the limit.
| 1 | import math |
| 2 | |
| 3 | |
| 4 | def count_div(x, a, b): |
| 5 | return x // a + x // b - x // math.lcm(a, b) |
| 6 | |
| 7 | |
| 8 | def distribute(n, limit): |
| 9 | total = 0 |
| 10 | for i in range(4): |
| 11 | rest = n - i * (limit + 1) |
| 12 | if rest >= 0: |
| 13 | total += (-1) ** i * math.comb(3, i) * math.comb(rest + 2, 2) |
| 14 | return total |
| 15 | |
| 16 | |
| 17 | print(count_div(30, 4, 6)) |
| 18 | print(distribute(6, 3), distribute(10, 4)) |
10 10 6
LeetCode problems that use it
- Nth Magical Number (LC 878): binary-search the smallest
xwithcount_div(x, a, b) >= n. - Ugly Number III (LC 1201): the same search with three numbers and seven terms.
- Distribute Candies Among Children II (LC 2929): count all splits of
namong 3 children, then subtract those where a child gets more thanlimit. - Rectangle Area (LC 223): the covered area is the two areas minus their overlap.
Counting pairs of equal items
A group of c equal items holds c(c - 1)/2 unordered pairs. So group the items by a key, then add this value for every group. This takes O(n) time instead of comparing all pairs.
| 1 | from collections import Counter |
| 2 | |
| 3 | |
| 4 | def good_pairs(nums): |
| 5 | return sum(c * (c - 1) // 2 for c in Counter(nums).values()) |
| 6 | |
| 7 | |
| 8 | print(good_pairs([2, 7, 2, 2, 7]), good_pairs([5, 5, 5, 5])) |
4 6
LeetCode problems that use it
How many subarrays contain index i
A subarray that contains index i starts at one of the i + 1 indices from 0 to i. It ends at one of the n - i indices from i to n - 1. So instead of listing all subarrays, add each element's value times the number of subarrays that contain it.
| 1 | def sum_of_subarray_sums(nums): |
| 2 | n = len(nums) |
| 3 | return sum(x * (i + 1) * (n - i) for i, x in enumerate(nums)) |
| 4 | |
| 5 | |
| 6 | def vowels_in_substrings(word): |
| 7 | n = len(word) |
| 8 | return sum((i + 1) * (n - i) for i, ch in enumerate(word) if ch in "aeiou") |
| 9 | |
| 10 | |
| 11 | print(sum_of_subarray_sums([1, 2, 3]), vowels_in_substrings("code")) |
20 10
LeetCode problems that use it
- Vowels of All Substrings (LC 2063): each vowel at index
iis in(i + 1)(n - i)substrings. - Sum of Subarray Minimums (LC 907): replace the two factors with the counts of subarrays where
arr[i]is the minimum. - Count Unique Characters of All Substrings of a Given String (LC 828): each character counts in the substrings that contain no other copy of it.
Card that teaches this method: Contribution Technique
A closed formula for a sum replaces a loop, and it often turns a search over values into one equation.
Sum 1 + 2 + … + n and other arithmetic series
Pair the first term with the last, the second with the second-to-last, and so on. Every pair has the same sum, n + 1. The second formula is the sum of k consecutive integers that start at a. It tells you which k and a can produce a given total.
| 1 | def missing_number(nums): |
| 2 | n = len(nums) |
| 3 | return n * (n + 1) // 2 - sum(nums) |
| 4 | |
| 5 | |
| 6 | def consecutive_sums(n): |
| 7 | count, k = 0, 1 |
| 8 | while k * (k + 1) // 2 <= n: |
| 9 | if (n - k * (k - 1) // 2) % k == 0: |
| 10 | count += 1 |
| 11 | k += 1 |
| 12 | return count |
| 13 | |
| 14 | |
| 15 | print(missing_number([3, 0, 1, 4]), consecutive_sums(21), consecutive_sums(32)) |
2 4 1
LeetCode problems that use it
- Missing Number (LC 268): the missing value is
n(n + 1)/2 - sum(nums). - Find the Pivot Integer (LC 2485): the pivot
xsatisfiesx * x == n(n + 1)/2. - Consecutive Numbers Sum (LC 829): for each length
k, check thatn - k(k - 1)/2is a positive multiple ofk. - Find Three Consecutive Integers That Sum to a Given Number (LC 2177): three consecutive integers sum to
3x + 3, sonummust be a multiple of 3.
Sum of squares
Use it when a total grows with the square of a size. With the plain sum, it also gives two equations for two unknown numbers. If dup appears twice and miss is missing from 1 to n, the plain sums give dup - miss and the square sums give dup² - miss². Dividing the second by the first gives dup + miss.
| 1 | def sum_squares(n): |
| 2 | return n * (n + 1) * (2 * n + 1) // 6 |
| 3 | |
| 4 | |
| 5 | def find_error_nums(nums): |
| 6 | n = len(nums) |
| 7 | diff = sum(nums) - n * (n + 1) // 2 |
| 8 | diff_sq = sum(x * x for x in nums) - sum_squares(n) |
| 9 | total = diff_sq // diff |
| 10 | dup = (diff + total) // 2 |
| 11 | return [dup, dup - diff] |
| 12 | |
| 13 | |
| 14 | print(sum_squares(10), find_error_nums([1, 3, 3, 4, 5]), find_error_nums([2, 2])) |
385 [3, 2] [2, 1]
Triangular and tetrahedral numbers
Tk is the number of boxes in a triangle with side k. Stacking the triangles with sides 1 to k gives a pyramid of k(k + 1)(k + 2)/6 boxes, with Tk boxes on the floor.
| 1 | def tetrahedral(k): |
| 2 | return k * (k + 1) * (k + 2) // 6 |
| 3 | |
| 4 | |
| 5 | print([tetrahedral(k) for k in range(1, 7)]) |
| 6 | print(sum(j * (j + 1) // 2 for j in range(1, 7))) |
[1, 4, 10, 20, 35, 56] 56
LeetCode problems that use it
- Building Boxes (LC 1739): find the largest full pyramid with at most
nboxes, then add floor boxes one at a time.
Geometric series
A number whose digits in base k are all 1 equals 1 + k + ... + k^m. For a fixed number of digits m + 1, this sum grows with k, and k is about the m-th root of n. So each digit count has at most one candidate base to check.
| 1 | def smallest_good_base(n): |
| 2 | for digits in range(n.bit_length(), 1, -1): |
| 3 | k = int(n ** (1 / (digits - 1))) |
| 4 | for base in (k - 1, k, k + 1): |
| 5 | if base >= 2 and (base**digits - 1) // (base - 1) == n: |
| 6 | return base |
| 7 | return n - 1 |
| 8 | |
| 9 | |
| 10 | print((3**5 - 1) // (3 - 1), sum(3**i for i in range(5))) |
| 11 | print(smallest_good_base(31), smallest_good_base(43), smallest_good_base(10**12 + 1)) |
121 121 2 6 1000000000000
LeetCode problems that use it
- Smallest Good Base (LC 483): for each digit count from the largest down, test the one candidate base.
Harmonic sum: loops over all multiples cost O(n log n)
A double loop that visits every multiple of every i from 1 to n runs n // i steps for each i. The total is about n ln n, not n². This is why a pass over all multiples of all numbers up to 105 is fast enough.
| 1 | import math |
| 2 | |
| 3 | n = 10**5 |
| 4 | steps = sum(n // i for i in range(1, n + 1)) |
| 5 | print(steps, round(n * math.log(n))) |
1166750 1151293
LeetCode problems that use it
- Graph Connectivity With Threshold (LC 1627): join every
z > thresholdwith all of its multiples.
Josephus recurrence
n people stand in a circle, numbered 0 to n - 1, and every k-th person leaves. J(n, k) is the 0-based position of the last person left. After the first removal, the circle of n - 1 people is the same problem shifted by k positions. So the recurrence runs in O(n) time with no simulation.
| 1 | def last_remaining(n, k): |
| 2 | pos = 0 |
| 3 | for size in range(2, n + 1): |
| 4 | pos = (pos + k) % size |
| 5 | return pos |
| 6 | |
| 7 | |
| 8 | print(last_remaining(7, 3) + 1, last_remaining(10, 2) + 1, last_remaining(1, 5) + 1) |
4 5 1
LeetCode problems that use it
- Find the Winner of the Circular Game (LC 1823): the winner is
J(n, k) + 1with 1-based numbering.
Card that teaches this method: Josephus Problem
These entries compute exact probabilities, or produce random choices that are exactly uniform.
Probability over equally likely moves
When every one of M moves has the same chance, the probability of success from state s is the average over the moves. Fill a table over states and remaining moves, like any other dynamic programming table.
| 1 | def knight_probability(n, k, row, col): |
| 2 | moves = [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)] |
| 3 | prob = [[1.0] * n for _ in range(n)] |
| 4 | for _ in range(k): |
| 5 | new = [[0.0] * n for _ in range(n)] |
| 6 | for r in range(n): |
| 7 | for c in range(n): |
| 8 | for dr, dc in moves: |
| 9 | if 0 <= r + dr < n and 0 <= c + dc < n: |
| 10 | new[r][c] += prob[r + dr][c + dc] / 8 |
| 11 | prob = new |
| 12 | return prob[row][col] |
| 13 | |
| 14 | |
| 15 | print(knight_probability(4, 1, 1, 1), knight_probability(5, 2, 2, 2)) |
0.5 0.375
Reservoir sampling: one uniform pick from a stream
Replace the kept item with the i-th item (1-based) with probability 1/i. Item j is kept at step j, and then survives every later step i with probability (i - 1)/i. The product telescopes, so every item ends with probability 1/n, and you never need to know n in advance.
| 1 | import random |
| 2 | from fractions import Fraction |
| 3 | |
| 4 | |
| 5 | def reservoir_pick(stream): |
| 6 | chosen = None |
| 7 | for i, item in enumerate(stream, start=1): |
| 8 | if random.randrange(i) == 0: |
| 9 | chosen = item |
| 10 | return chosen |
| 11 | |
| 12 | |
| 13 | def exact_keep_probability(j, n): |
| 14 | p = Fraction(1, j) |
| 15 | for i in range(j + 1, n + 1): |
| 16 | p *= Fraction(i - 1, i) |
| 17 | return p |
| 18 | |
| 19 | |
| 20 | print([str(exact_keep_probability(j, 5)) for j in range(1, 6)]) |
['1/5', '1/5', '1/5', '1/5', '1/5']
LeetCode problems that use it
- Linked List Random Node (LC 382): pick a random node of a linked list of unknown length.
- Random Pick Index (LC 398): pick a random index among the positions that hold the target.
Card that teaches this method: Reservoir Sampling
Weighted random choice with prefix sums
Lay the weights end to end on a line of length W, the total weight. Index i owns wi of the W positions. A uniform r from 1 to W, searched with bisect_left in the prefix sums, lands in index i with probability wi / W.
| 1 | import random |
| 2 | from bisect import bisect_left |
| 3 | from collections import Counter |
| 4 | from itertools import accumulate |
| 5 | |
| 6 | |
| 7 | def pick_index(prefix, r): |
| 8 | return bisect_left(prefix, r) |
| 9 | |
| 10 | |
| 11 | weights = [1, 3, 2] |
| 12 | prefix = list(accumulate(weights)) |
| 13 | print(prefix, pick_index(prefix, random.randint(1, prefix[-1])) in range(3)) |
| 14 | print(Counter(pick_index(prefix, r) for r in range(1, prefix[-1] + 1))) |
[1, 4, 6] True
Counter({1: 3, 2: 2, 0: 1})LeetCode problems that use it
- Random Pick with Weight (LC 528): search a uniform random number in the prefix sums.
- Random Point in Non-overlapping Rectangles (LC 497): weight each rectangle by its number of integer points,
(x2 - x1 + 1) * (y2 - y1 + 1).
Card that teaches this method: Lower Bound
Fisher–Yates shuffle
Each step picks the item for position i uniformly from the items not yet placed. The choices multiply to n! equally likely sequences, and each sequence gives a different permutation. So every permutation has probability 1/n!. Picking j from the whole array at every step is a known mistake. It gives n^n equally likely sequences. For n >= 3 that number is not a multiple of n!, so the permutations cannot all be equally likely.
| 1 | import random |
| 2 | from itertools import product |
| 3 | |
| 4 | |
| 5 | def shuffle(a): |
| 6 | for i in range(len(a) - 1, 0, -1): |
| 7 | j = random.randint(0, i) |
| 8 | a[i], a[j] = a[j], a[i] |
| 9 | return a |
| 10 | |
| 11 | |
| 12 | def all_outcomes(n): |
| 13 | results = [] |
| 14 | for choices in product(*[range(i + 1) for i in range(n - 1, 0, -1)]): |
| 15 | a = list(range(n)) |
| 16 | for i, j in zip(range(n - 1, 0, -1), choices): |
| 17 | a[i], a[j] = a[j], a[i] |
| 18 | results.append(tuple(a)) |
| 19 | return results |
| 20 | |
| 21 | |
| 22 | outcomes = all_outcomes(4) |
| 23 | print(len(outcomes), len(set(outcomes)), sorted(shuffle([1, 2, 3]))) |
24 24 [1, 2, 3]
Rejection sampling
Two calls of a uniform 1-to-7 generator give 49 equally likely values v. Keep v only when it is at most 40, and return (v - 1) % 10 + 1. Each result from 1 to 10 then comes from exactly 4 of the 40 kept values. A round succeeds with probability p = 40/49, so the expected number of rounds is 1/p = 49/40.
| 1 | from collections import Counter |
| 2 | |
| 3 | |
| 4 | def rand10_from(a, b): |
| 5 | v = 7 * (a - 1) + b |
| 6 | return (v - 1) % 10 + 1 if v <= 40 else None |
| 7 | |
| 8 | |
| 9 | results = Counter(rand10_from(a, b) for a in range(1, 8) for b in range(1, 8)) |
| 10 | print(results[None], sorted(set(results.values()) - {results[None]})) |
9 [4]
Games decided by a simple rule
A position is winning when some move leads to a losing position for the opponent. Some games have a short answer. In the Nim game with 1 to 3 stones per turn, you lose exactly when n is a multiple of 4. If you take t stones, the opponent takes 4 - t, and n stays a multiple of 4. Compute the game table for small inputs first, and look for the pattern.
| 1 | def winners(limit, moves): |
| 2 | win = [False] * (limit + 1) |
| 3 | for n in range(1, limit + 1): |
| 4 | win[n] = any(not win[n - m] for m in moves(n) if m <= n) |
| 5 | return win |
| 6 | |
| 7 | |
| 8 | nim = winners(12, lambda n: [1, 2, 3]) |
| 9 | divisor = winners(12, lambda n: [x for x in range(1, n) if n % x == 0]) |
| 10 | print([n for n in range(1, 13) if not nim[n]]) |
| 11 | print([n for n in range(1, 13) if divisor[n]]) |
[4, 8, 12] [2, 4, 6, 8, 10, 12]
LeetCode problems that use it
- Nim Game (LC 292): you win when
n % 4 != 0. - Divisor Game (LC 1025): the first player wins when
nis even. - Stone Game (LC 877): the number of piles is even and the total is odd. The first player can take all the even-indexed piles or all the odd-indexed piles. One of these two sets has the larger sum, so the first player always wins.
Card that teaches this method: Minimax DP
These identities work on the binary form of integers. In Python a negative integer behaves as if it had infinitely many leading 1 bits.
Powers of two, four and three
n - 1 flips the lowest 1 bit of n and every 0 bit below it. So n & (n - 1) clears the lowest 1 bit, and the result is 0 only when n had one 1 bit. A power of four is a power of two whose 1 bit is at an even position, so n & 0x55555555 != 0. For three there is no bit test. Inside the 32-bit range, 319 = 1162261467 is the largest power of three, so n is a power of three when n > 0 and 1162261467 % n == 0.
| 1 | def is_power_of_two(n): |
| 2 | return n > 0 and n & (n - 1) == 0 |
| 3 | |
| 4 | |
| 5 | def is_power_of_four(n): |
| 6 | return is_power_of_two(n) and n & 0x55555555 != 0 |
| 7 | |
| 8 | |
| 9 | def is_power_of_three(n): |
| 10 | return n > 0 and 3**19 % n == 0 |
| 11 | |
| 12 | |
| 13 | print(is_power_of_two(64), is_power_of_two(96)) |
| 14 | print(is_power_of_four(64), is_power_of_four(32)) |
| 15 | print(3**19, is_power_of_three(243), is_power_of_three(45)) |
True False True False 1162261467 True False
LeetCode problems that use it
- Power of Two (LC 231): test
n & (n - 1) == 0for a positiven. - Power of Four (LC 342): add the even-position mask
0x55555555. - Power of Three (LC 326): check that
ndivides319.
Card that teaches this method: Kernighan's Bit Trick
Lowest set bit
In two's complement, -x equals ~x + 1. Below the lowest 1 bit of x, x has only 0 bits, so ~x has only 1 bits there. Adding 1 turns those 1 bits into 0 and sets the lowest 1 bit of x. Above that bit, -x is ~x, so it differs from x in every bit. So x & -x keeps exactly one bit, the lowest 1 bit of x.
| 1 | def single_number_iii(nums): |
| 2 | xor_all = 0 |
| 3 | for x in nums: |
| 4 | xor_all ^= x |
| 5 | low = xor_all & -xor_all |
| 6 | a = 0 |
| 7 | for x in nums: |
| 8 | if x & low: |
| 9 | a ^= x |
| 10 | return sorted([a, xor_all ^ a]) |
| 11 | |
| 12 | |
| 13 | print(12 & -12, 12 & 11, single_number_iii([4, 7, 4, 9, 2, 2])) |
4 8 [7, 9]
LeetCode problems that use it
- Single Number III (LC 260): split the numbers by the lowest bit where the two answers differ.
- Range Sum Query - Mutable (LC 307): a Fenwick tree moves between indices with
i += i & -iandi -= i & -i.
Card that teaches this method: Fenwick Tree
XOR cancels equal values
XOR is commutative and associative, and a value XORed with itself is 0. So in the XOR of a list, every value that appears twice cancels. P[i] is the XOR of the first i values. The XOR of a range is the XOR of two prefix values, exactly like a range sum.
| 1 | from functools import reduce |
| 2 | from operator import xor |
| 3 | |
| 4 | |
| 5 | def single_number(nums): |
| 6 | return reduce(xor, nums) |
| 7 | |
| 8 | |
| 9 | def range_xor(nums, queries): |
| 10 | prefix = [0] |
| 11 | for x in nums: |
| 12 | prefix.append(prefix[-1] ^ x) |
| 13 | return [prefix[r + 1] ^ prefix[l] for l, r in queries] |
| 14 | |
| 15 | |
| 16 | print(single_number([6, 3, 6, 8, 3])) |
| 17 | print(range_xor([5, 2, 7, 1], [[0, 1], [1, 3], [2, 2]])) |
8 [7, 4, 7]
LeetCode problems that use it
- Single Number (LC 136): XOR all values, and the pairs cancel.
- Missing Number (LC 268): XOR the values with every index from 0 to
n. - XOR Queries of a Subarray (LC 1310): answer each query with two prefix XOR values.
- Find The Original Array of Prefix Xor (LC 2433):
arr[i] = pref[i] ^ pref[i - 1].
Card that teaches this method: XOR Cancel
Counting 1 bits
The loop x &= x - 1 removes one 1 bit per round, so it runs once per 1 bit. The recurrence fills a table of counts for 0 to n in O(n) time. Python 3.10 and newer also have x.bit_count(). bin(x).count("1") works in every version.
| 1 | def popcount(x): |
| 2 | count = 0 |
| 3 | while x: |
| 4 | x &= x - 1 |
| 5 | count += 1 |
| 6 | return count |
| 7 | |
| 8 | |
| 9 | def count_bits(n): |
| 10 | bits = [0] * (n + 1) |
| 11 | for i in range(1, n + 1): |
| 12 | bits[i] = bits[i >> 1] + (i & 1) |
| 13 | return bits |
| 14 | |
| 15 | |
| 16 | print(popcount(13), bin(13).count("1"), count_bits(8), popcount(3 ^ 12)) |
3 3 [0, 1, 1, 2, 1, 2, 2, 3, 1] 4
LeetCode problems that use it
- Number of 1 Bits (LC 191): remove one 1 bit per round.
- Counting Bits (LC 338): fill the table with
bits[i >> 1] + (i & 1). - Hamming Distance (LC 461): the Hamming distance is the popcount of
x ^ y.
Card that teaches this method: Kernighan's Bit Trick
Adding two integers with bit operations
a ^ b adds each bit position without carries. (a & b) << 1 holds the carries, moved one position left. Repeat with these two numbers until the carry is 0. Python integers have no fixed width, so keep 32 bits with a mask and convert the result back to a signed value at the end.
| 1 | MASK = 0xFFFFFFFF |
| 2 | |
| 3 | |
| 4 | def get_sum(a, b): |
| 5 | while b & MASK: |
| 6 | a, b = (a ^ b) & MASK, ((a & b) << 1) & MASK |
| 7 | a &= MASK |
| 8 | return a if a <= 0x7FFFFFFF else ~(a ^ MASK) |
| 9 | |
| 10 | |
| 11 | print(get_sum(7, 8), get_sum(-5, 3), get_sum(-8, -9)) |
15 -2 -17
LeetCode problems that use it
- Sum of Two Integers (LC 371): repeat the XOR and carry steps inside a 32-bit mask.
Logarithms count how many times a size can be halved or multiplied. They set the cost of binary search and of divide-and-conquer.
Logarithms, digit counts and bit lengths
A floating-point logarithm can be slightly wrong, so never use it to test whether a number is an exact power. math.log(243, 3) returns a value just below 5. For counts, use integer tools. len(str(n)) counts decimal digits and n.bit_length() counts bits.
| 1 | import math |
| 2 | |
| 3 | print(math.log(243, 3), 3**5 == 243) |
| 4 | n = 1000 |
| 5 | print(len(str(n)), n.bit_length(), math.floor(math.log2(n)) + 1) |
4.999999999999999 True 4 10 10
Master theorem for divide and conquer
a is the number of recursive calls, n/b the size of each call, and O(n^d) the work outside the calls. Compare d with log_b a to see whether the top level, every level equally, or the leaves dominate the total work.
| 1 | from functools import lru_cache |
| 2 | |
| 3 | |
| 4 | @lru_cache(maxsize=None) |
| 5 | def merge_sort_work(n): |
| 6 | return n if n <= 1 else 2 * merge_sort_work(n // 2) + n |
| 7 | |
| 8 | |
| 9 | @lru_cache(maxsize=None) |
| 10 | def fast_power_steps(n): |
| 11 | return 1 if n <= 1 else fast_power_steps(n // 2) + 1 |
| 12 | |
| 13 | |
| 14 | n = 2**20 |
| 15 | print(merge_sort_work(n) == n * 20 + n, fast_power_steps(n)) |
True 21
How many probes a binary search makes
Each probe of a binary search over N sorted items discards the middle item and at least half of the rest. So the search makes at most N.bit_length() probes. For N = 106 that is 20 probes.
| 1 | def probes(nums, target): |
| 2 | lo, hi, count = 0, len(nums) - 1, 0 |
| 3 | while lo <= hi: |
| 4 | mid = (lo + hi) // 2 |
| 5 | count += 1 |
| 6 | if nums[mid] == target: |
| 7 | break |
| 8 | if nums[mid] < target: |
| 9 | lo = mid + 1 |
| 10 | else: |
| 11 | hi = mid - 1 |
| 12 | return count |
| 13 | |
| 14 | |
| 15 | nums = list(range(1000)) |
| 16 | print(max(probes(nums, t) for t in range(-1, 1001)), len(nums).bit_length()) |
| 17 | print((10**6).bit_length()) |
10 10 20
LeetCode problems that use it
- Binary Search (LC 704): the search makes at most
len(nums).bit_length()probes.
Card that teaches this method: Exact Match
These formulas map positions in a grid with m rows and n columns to other positions, so a transformation needs no extra search.
Transpose and rotate a matrix
Rotating a square matrix clockwise moves the value at row i, column j to row j, column n - 1 - i. In place, that is a transpose followed by reversing every row. A transpose of a matrix that is not square changes its shape to n rows and m columns, so build a new matrix with zip(*matrix).
| 1 | def rotate(matrix): |
| 2 | n = len(matrix) |
| 3 | for i in range(n): |
| 4 | for j in range(i + 1, n): |
| 5 | matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] |
| 6 | for row in matrix: |
| 7 | row.reverse() |
| 8 | return matrix |
| 9 | |
| 10 | |
| 11 | print(rotate([[1, 2], [3, 4]])) |
| 12 | print([list(col) for col in zip(*[[1, 2], [3, 4], [5, 6]])]) |
[[3, 1], [4, 2]] [[1, 3, 5], [2, 4, 6]]
Spiral order with four boundaries
Keep four boundaries, top, bottom, left and right. Walk the top row, the right column, the bottom row and the left column. After each side, move its boundary one step inward. Check the boundaries again before the bottom row and the left column, because a single remaining row or column must not be walked twice. The loop runs once per ring, and a grid with m rows and n columns has ⌈min(m, n)/2⌉ rings.
| 1 | def spiral_order(matrix): |
| 2 | top, bottom, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1 |
| 3 | out = [] |
| 4 | while top <= bottom and left <= right: |
| 5 | out += [matrix[top][c] for c in range(left, right + 1)] |
| 6 | top += 1 |
| 7 | out += [matrix[r][right] for r in range(top, bottom + 1)] |
| 8 | right -= 1 |
| 9 | if top <= bottom: |
| 10 | out += [matrix[bottom][c] for c in range(right, left - 1, -1)] |
| 11 | bottom -= 1 |
| 12 | if left <= right: |
| 13 | out += [matrix[r][left] for r in range(bottom, top - 1, -1)] |
| 14 | left += 1 |
| 15 | return out |
| 16 | |
| 17 | |
| 18 | print(spiral_order([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])) |
[1, 2, 3, 6, 9, 12, 11, 10, 7, 4, 5, 8]
LeetCode problems that use it
- Spiral Matrix (LC 54): read the values layer by layer.
- Spiral Matrix II (LC 59): write 1 to
n * nalong the same path. - Spiral Matrix IV (LC 2326): write the linked-list values along the same path.
Card that teaches this method: Spiral Matrix
Diagonals: r − c and r + c are constant
Moving one step down and one step right keeps r - c the same. Moving one step down and one step left keeps r + c the same. So either value is a key that groups the cells of a diagonal. An m by n grid has m + n - 1 diagonals in each direction.
| 1 | from collections import defaultdict |
| 2 | |
| 3 | |
| 4 | def group_diagonals(mat): |
| 5 | groups = defaultdict(list) |
| 6 | for r, row in enumerate(mat): |
| 7 | for c, value in enumerate(row): |
| 8 | groups[r - c].append(value) |
| 9 | return dict(groups) |
| 10 | |
| 11 | |
| 12 | def is_toeplitz(mat): |
| 13 | return len(group_diagonals(mat)) == sum( |
| 14 | len(set(v)) for v in group_diagonals(mat).values() |
| 15 | ) |
| 16 | |
| 17 | |
| 18 | print(group_diagonals([[1, 2, 3], [4, 5, 6]])) |
| 19 | print(is_toeplitz([[1, 2, 3], [4, 1, 2], [5, 4, 1]]), is_toeplitz([[1, 2], [3, 3]])) |
{0: [1, 5], -1: [2, 6], -2: [3], 1: [4]}
True FalseLeetCode problems that use it
- Diagonal Traverse (LC 498): walk the anti-diagonals
r + c = 0, 1, ..., reversing every other one. - Sort the Matrix Diagonally (LC 1329): sort each group of cells with the same
r - c. - Toeplitz Matrix (LC 766): every group with the same
r - cholds one value. - N-Queens (LC 51): a queen blocks its row, its column, its
r - cdiagonal and itsr + cdiagonal.
Card that teaches this method: Canonical Key
Flatten a 2D index into one number
Reading an m by n grid row by row gives cell (r, c) the position k = r * n + c. divmod(k, n) converts back. This lets a binary search, a reshape or a random pick treat the grid as one list of m * n cells.
| 1 | def search_matrix(matrix, target): |
| 2 | m, n = len(matrix), len(matrix[0]) |
| 3 | lo, hi = 0, m * n - 1 |
| 4 | while lo <= hi: |
| 5 | mid = (lo + hi) // 2 |
| 6 | r, c = divmod(mid, n) |
| 7 | if matrix[r][c] == target: |
| 8 | return True |
| 9 | if matrix[r][c] < target: |
| 10 | lo = mid + 1 |
| 11 | else: |
| 12 | hi = mid - 1 |
| 13 | return False |
| 14 | |
| 15 | |
| 16 | grid = [[2, 4, 6], [8, 10, 12], [14, 16, 18]] |
| 17 | print(divmod(7, 3), search_matrix(grid, 10), search_matrix(grid, 11)) |
(2, 1) True False
LeetCode problems that use it
- Search a 2D Matrix (LC 74): binary-search the flattened index from 0 to
m * n - 1. - Reshape the Matrix (LC 566): copy value
kof the old shape todivmod(k, c)of the new shape. - Random Flip Matrix (LC 519): pick a random flattened index, then convert it to a cell.
Card that teaches this method: Exact Match
Short facts that decide whether a loop ends, how many values a range holds, and how to compare fractions without rounding.
Remainders repeat within k steps
There are only k possible remainders modulo k. So among k + 1 remainders, two are equal (the pigeonhole principle). When the next remainder depends only on the current one, the sequence repeats from that point. So a search over remainders can stop after k steps.
| 1 | def smallest_repunit_div_by_k(k): |
| 2 | r = 0 |
| 3 | for length in range(1, k + 1): |
| 4 | r = (r * 10 + 1) % k |
| 5 | if r == 0: |
| 6 | return length |
| 7 | return -1 |
| 8 | |
| 9 | |
| 10 | print(smallest_repunit_div_by_k(7), smallest_repunit_div_by_k(41)) |
| 11 | print(smallest_repunit_div_by_k(10)) |
6 5 -1
The smallest missing positive is between 1 and n + 1
n numbers can cover at most the n values 1 to n. So the answer is one of 1 to n + 1. Because only the values 1 to n matter, the array itself can serve as the table. Place each value v from 1 to n at index v - 1. Then the answer is i + 1 for the first index i where nums[i] != i + 1.
| 1 | def first_missing_positive(nums): |
| 2 | n = len(nums) |
| 3 | for i in range(n): |
| 4 | while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]: |
| 5 | j = nums[i] - 1 |
| 6 | nums[i], nums[j] = nums[j], nums[i] |
| 7 | for i in range(n): |
| 8 | if nums[i] != i + 1: |
| 9 | return i + 1 |
| 10 | return n + 1 |
| 11 | |
| 12 | |
| 13 | print(first_missing_positive([2, 5, -3, 1, 7]), first_missing_positive([1, 2, 3])) |
3 4
LeetCode problems that use it
- First Missing Positive (LC 41): place every value from 1 to
nat its own index. - Find All Numbers Disappeared in an Array (LC 448): the missing values are the indices that do not hold their value.
Card that teaches this method: Cyclic Sort
Long multiplication of digit strings
Number the digits from the left, starting at 0. A product of an m-digit and an n-digit number has at most m + n digits. The product of digit i and digit j adds to position i + j + 1, and its carry goes to position i + j.
| 1 | def multiply(a, b): |
| 2 | res = [0] * (len(a) + len(b)) |
| 3 | for i in range(len(a) - 1, -1, -1): |
| 4 | for j in range(len(b) - 1, -1, -1): |
| 5 | total = res[i + j + 1] + int(a[i]) * int(b[j]) |
| 6 | res[i + j + 1] = total % 10 |
| 7 | res[i + j] += total // 10 |
| 8 | return "".join(map(str, res)).lstrip("0") or "0" |
| 9 | |
| 10 | |
| 11 | print(multiply("123", "45"), multiply("99", "99"), multiply("0", "52")) |
5535 9801 0
LeetCode problems that use it
- Multiply Strings (LC 43): fill the
m + ndigit array, then remove leading zeros.
Counting in a range, and rounding a division up
The multiples of k from 1 to hi number hi // k. Subtract the ones below lo. The odd numbers from low to high number (high + 1) // 2 - low // 2. For a positive b, round a division up with (a + b - 1) // b or -(-a // b). Both stay in integers, unlike math.ceil(a / b), which goes through a float. Splitting a pile of n into pieces no larger than L takes one fewer split than the ceil(n / L) pieces that produces, and substituting the ceiling-division identity above and canceling the Ls collapses ceil(n / L) - 1 down to the single floor division (n - 1) // L.
| 1 | def count_multiples(lo, hi, k): |
| 2 | return hi // k - (lo - 1) // k |
| 3 | |
| 4 | |
| 5 | def count_odds(low, high): |
| 6 | return (high + 1) // 2 - low // 2 |
| 7 | |
| 8 | |
| 9 | def ceil_div(a, b): |
| 10 | return -(-a // b) |
| 11 | |
| 12 | |
| 13 | def splits_needed(n, limit): |
| 14 | return (n - 1) // limit |
| 15 | |
| 16 | |
| 17 | print(count_multiples(10, 30, 7), count_odds(4, 11), count_odds(5, 5)) |
| 18 | print(ceil_div(7, 3), ceil_div(6, 3), ceil_div(10**18 + 1, 1)) |
| 19 | print(splits_needed(9, 3), splits_needed(9, 2)) |
3 4 1 3 2 1000000000000000001 2 4
LeetCode problems that use it
- Count Odd Numbers in an Interval Range (LC 1523): the count is
(high + 1) // 2 - low // 2. - Koko Eating Bananas (LC 875): a pile of
pbananas takesceil(p / k)hours. - Minimum Speed to Arrive on Time (LC 1870): each ride except the last takes a whole number of hours, rounded up.
- Minimum Limit of Balls in a Bag (LC 1760): splitting a bag of
nballs down to a limit ofLtakes(n - 1) // Loperations.
Card that teaches this method: Feasible Function
Comparing fractions exactly
Two different fractions with large numerators and denominators can round to the same float. Cross-multiplying keeps the comparison in integers, which Python computes exactly. fractions.Fraction does the same and also reduces the fraction.
| 1 | from fractions import Fraction |
| 2 | |
| 3 | a, b = 999999999, 999999998 |
| 4 | c, d = 999999998, 999999997 |
| 5 | print(a / b == c / d, a * d == c * b) |
| 6 | print(Fraction(a, b) == Fraction(c, d), Fraction(6, 8)) |
True False False 3/4
LeetCode problems that use it
- Minimum Lines to Represent a Line Chart (LC 2280): compare the slopes of neighbouring segments by cross-multiplying.
- K-th Smallest Prime Fraction (LC 786): keep the largest fraction
arr[i - 1] / arr[j]found so far by testingarr[i - 1] * best[1] > best[0] * arr[j]. Both denominators are positive, so the direction of>is kept.