Super Ugly Number
The nth number whose prime factors lie in a given list — k-way merge of multiples.
4 min read
k-way-merge dp heaps
Problem#
A super ugly number has all prime factors in the given list primes. Return the nth such number (1-indexed; 1 counts).
Examples#
n = 12, primes = [2,7,13,19]→32
Constraints#
1 <= n <= 10^5,1 <= primes.length <= 100.
Approach#
DP with k pointers: dp[i] is the i-th super ugly. For each prime p, maintain a pointer idx[p] such that the next multiple is p * dp[idx[p]]. Pick the minimum across primes; advance every pointer whose product equals that minimum (to dedup).
Solution#
class Solution {public: int nthSuperUglyNumber(int n, vector<int>& primes) { int k = primes.size(); vector<int> dp(n); vector<int> idx(k, 0); dp[0] = 1; for (int i = 1; i < n; ++i) { int next = INT_MAX; for (int j = 0; j < k; ++j) next = min(next, primes[j] * dp[idx[j]]); dp[i] = next; for (int j = 0; j < k; ++j) if (primes[j] * dp[idx[j]] == next) ++idx[j]; } return dp[n - 1]; }};import "math"
func nthSuperUglyNumber(n int, primes []int) int { k := len(primes) dp := make([]int, n) idx := make([]int, k) dp[0] = 1 for i := 1; i < n; i++ { next := math.MaxInt32 for j := 0; j < k; j++ { v := primes[j] * dp[idx[j]] if v < next { next = v } } dp[i] = next for j := 0; j < k; j++ { if primes[j]*dp[idx[j]] == next { idx[j]++ } } } return dp[n-1]}from typing import List
class Solution: def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int: k = len(primes) dp = [0] * n idx = [0] * k dp[0] = 1 for i in range(1, n): nxt = min(primes[j] * dp[idx[j]] for j in range(k)) dp[i] = nxt for j in range(k): if primes[j] * dp[idx[j]] == nxt: idx[j] += 1 return dp[n - 1]var nthSuperUglyNumber = function(n, primes) { const k = primes.length; const dp = new Array(n).fill(0); const idx = new Array(k).fill(0); dp[0] = 1; for (let i = 1; i < n; i++) { let next = Infinity; for (let j = 0; j < k; j++) { next = Math.min(next, primes[j] * dp[idx[j]]); } dp[i] = next; for (let j = 0; j < k; j++) { if (primes[j] * dp[idx[j]] === next) idx[j]++; } } return dp[n - 1];};class Solution { public int nthSuperUglyNumber(int n, int[] primes) { int k = primes.length; int[] dp = new int[n]; int[] idx = new int[k]; dp[0] = 1; for (int i = 1; i < n; i++) { int next = Integer.MAX_VALUE; for (int j = 0; j < k; j++) { next = Math.min(next, primes[j] * dp[idx[j]]); } dp[i] = next; for (int j = 0; j < k; j++) { if (primes[j] * dp[idx[j]] == next) idx[j]++; } } return dp[n - 1]; }}function nthSuperUglyNumber(n: number, primes: number[]): number { const k: number = primes.length; const dp: number[] = new Array(n).fill(0); const idx: number[] = new Array(k).fill(0); dp[0] = 1; for (let i = 1; i < n; i++) { let next: number = Infinity; for (let j = 0; j < k; j++) { next = Math.min(next, primes[j] * dp[idx[j]]); } dp[i] = next; for (let j = 0; j < k; j++) { if (primes[j] * dp[idx[j]] === next) idx[j]++; } } return dp[n - 1];}Editorial#
Each pointer idx[j] represents “the next dp-entry we’d multiply by primes[j]”. The frontier of k candidates is the heads of k merge streams. Advancing every pointer with the same candidate value dedups (e.g. 2*3 == 3*2 produces 6 only once).
Complexity#
- Time: O(n * k).
- Space: O(n + k).
Concept revision#
Related#