Intuition
Testing every integer in turn to see whether it factors into only 2s, 3s, and 5s is far too slow once n grows. Build the sequence forward instead: every ugly number, after the first, is some earlier ugly number multiplied by 2, 3, or 5. Keep three pointers into the sequence built so far, one per factor, and at each step take the minimum of the three candidate products. The easy mistake is advancing only the pointer that produced the minimum -- when two or three candidates tie for the minimum, EVERY pointer that produced it must advance, or a value like 6 (which is both 2x3 and 3x2) gets appended twice.
Approach
Seed the sequence and three pointers
Start the sequence with just [1], since 1 is ugly by definition, and initialize three pointers p2, p3, p5 all at index 0 -- each pointing at the earlier ugly number that factor should multiply next.
Repeatedly take the minimum of three candidates
At each step compute dp[p2]2, dp[p3]3, dp[p5]*5, take their minimum, and append it to the sequence. This minimum is guaranteed to be the next ugly number, since nothing smaller can be built from the numbers already in the sequence.
Advance every pointer that matched the minimum
Compare the minimum against all three candidates, not just the one that happened to be checked first, and increment every pointer whose candidate equals the minimum. This is what prevents duplicate values like 6 or 12 from being inserted more than once.
Solution & live demo
Edge cases
the sequence already contains just [1] and no loop iterations run, so the answer is 1
e.g. dp[p2]2 == dp[p3]3 == 6, both pointers advance in the same step, not just one
the sequence grows by exactly one entry per iteration, so building it takes O(n) total work
the other two pointers simply sit still until their candidate becomes the minimum again