Overflow in intermediate arithmetic
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.
Move from brute-force thinking to an efficient approach using math strategy.
An ugly number is a positive integer that is divisible by a, b, or c.
Given four integers n, a, b, and c, return the nth ugly number.
Example 1:
Input: n = 3, a = 2, b = 3, c = 5 Output: 4 Explanation: The ugly numbers are 2, 3, 4, 5, 6, 8, 9, 10... The 3rd is 4.
Example 2:
Input: n = 4, a = 2, b = 3, c = 4 Output: 6 Explanation: The ugly numbers are 2, 3, 4, 6, 8, 9, 10, 12... The 4th is 6.
Example 3:
Input: n = 5, a = 2, b = 11, c = 13 Output: 10 Explanation: The ugly numbers are 2, 4, 6, 8, 10, 11, 12, 13... The 5th is 10.
Constraints:
1 <= n, a, b, c <= 1091 <= a * b * c <= 1018[1, 2 * 109].Problem summary: An ugly number is a positive integer that is divisible by a, b, or c. Given four integers n, a, b, and c, return the nth ugly number.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Math · Binary Search
3 2 3 5
4 2 3 4
5 2 11 13
ugly-number-ii)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #1201: Ugly Number III
class Solution {
public int nthUglyNumber(int n, int a, int b, int c) {
long ab = lcm(a, b);
long bc = lcm(b, c);
long ac = lcm(a, c);
long abc = lcm(ab, c);
long l = 1, r = 2000000000;
while (l < r) {
long mid = (l + r) >> 1;
if (mid / a + mid / b + mid / c - mid / ab - mid / bc - mid / ac + mid / abc >= n) {
r = mid;
} else {
l = mid + 1;
}
}
return (int) l;
}
private long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
private long lcm(long a, long b) {
return a * b / gcd(a, b);
}
}
// Accepted solution for LeetCode #1201: Ugly Number III
func nthUglyNumber(n int, a int, b int, c int) int {
ab, bc, ac := lcm(a, b), lcm(b, c), lcm(a, c)
abc := lcm(ab, c)
var l, r int = 1, 2e9
for l < r {
mid := (l + r) >> 1
if mid/a+mid/b+mid/c-mid/ab-mid/bc-mid/ac+mid/abc >= n {
r = mid
} else {
l = mid + 1
}
}
return l
}
func gcd(a, b int) int {
if b == 0 {
return a
}
return gcd(b, a%b)
}
func lcm(a, b int) int {
return a * b / gcd(a, b)
}
# Accepted solution for LeetCode #1201: Ugly Number III
class Solution:
def nthUglyNumber(self, n: int, a: int, b: int, c: int) -> int:
ab = lcm(a, b)
bc = lcm(b, c)
ac = lcm(a, c)
abc = lcm(a, b, c)
l, r = 1, 2 * 10**9
while l < r:
mid = (l + r) >> 1
if (
mid // a
+ mid // b
+ mid // c
- mid // ab
- mid // bc
- mid // ac
+ mid // abc
>= n
):
r = mid
else:
l = mid + 1
return l
// Accepted solution for LeetCode #1201: Ugly Number III
struct Solution;
impl Solution {
fn nth_ugly_number(n: i32, a: i32, b: i32, c: i32) -> i32 {
let mut left = 0;
let mut right = 2_000_000_000;
while left < right {
let mid = left + (right - left) / 2;
if Self::count(mid, a as u64, b as u64, c as u64) < n as u64 {
left = mid + 1;
} else {
right = mid
}
}
left as i32
}
fn count(num: u64, a: u64, b: u64, c: u64) -> u64 {
num / a + num / b + num / c
- num / Self::lcm(a, b)
- num / Self::lcm(b, c)
- num / Self::lcm(a, c)
+ num / Self::lcm(a, Self::lcm(b, c))
}
fn lcm(a: u64, b: u64) -> u64 {
a * b / Self::gcd(a, b)
}
fn gcd(a: u64, b: u64) -> u64 {
if a == 0 {
b
} else {
Self::gcd(b % a, a)
}
}
}
#[test]
fn test() {
let n = 3;
let a = 2;
let b = 3;
let c = 5;
let res = 4;
assert_eq!(Solution::nth_ugly_number(n, a, b, c), res);
let n = 4;
let a = 2;
let b = 3;
let c = 4;
let res = 6;
assert_eq!(Solution::nth_ugly_number(n, a, b, c), res);
let n = 5;
let a = 2;
let b = 11;
let c = 13;
let res = 10;
assert_eq!(Solution::nth_ugly_number(n, a, b, c), res);
let n = 1000000000;
let a = 2;
let b = 217983653;
let c = 336916467;
let res = 1999999984;
assert_eq!(Solution::nth_ugly_number(n, a, b, c), res);
}
// Accepted solution for LeetCode #1201: Ugly Number III
function nthUglyNumber(n: number, a: number, b: number, c: number): number {
const ab = lcm(BigInt(a), BigInt(b));
const bc = lcm(BigInt(b), BigInt(c));
const ac = lcm(BigInt(a), BigInt(c));
const abc = lcm(BigInt(a), bc);
let l = 1n;
let r = BigInt(2e9);
while (l < r) {
const mid = (l + r) >> 1n;
const count =
mid / BigInt(a) +
mid / BigInt(b) +
mid / BigInt(c) -
mid / ab -
mid / bc -
mid / ac +
mid / abc;
if (count >= BigInt(n)) {
r = mid;
} else {
l = mid + 1n;
}
}
return Number(l);
}
function gcd(a: bigint, b: bigint): bigint {
return b === 0n ? a : gcd(b, a % b);
}
function lcm(a: bigint, b: bigint): bigint {
return (a * b) / gcd(a, b);
}
Use this to step through a reusable interview workflow for this problem.
Check every element from left to right until we find the target or exhaust the array. Each comparison is O(1), and we may visit all n elements, giving O(n). No extra space needed.
Each comparison eliminates half the remaining search space. After k comparisons, the space is n/2ᵏ. We stop when the space is 1, so k = log₂ n. No extra memory needed — just two pointers (lo, hi).
Review these before coding to avoid predictable interview regressions.
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.
Wrong move: Setting `lo = mid` or `hi = mid` can stall and create an infinite loop.
Usually fails on: Two-element ranges never converge.
Fix: Use `lo = mid + 1` or `hi = mid - 1` where appropriate.