简体   繁体   中英

How to optimize an algorithm with multiple while loops?

I am trying to solve a problem on leetcode.com Ugly Number II .

problem: An ugly number is a positive integer whose prime factors are limited to 2, 3, and 5.

Given an integer n, return the nth ugly number.

example:

Input: n = 10 Output: 12 Explanation: [1, 2, 3, 4, 5, 6, 8, 9, 10, 12] is the sequence of the first 10 ugly numbers.

This is my solution

class Solution {
    public int nthUglyNumber(int n) {
        int outputNumber = 6;
        int temp = 1;
        if (n < 7) {
            return n;
        }
        int i = 7;
        while (i != (n + 1)) {
            outputNumber = outputNumber + 1;
            temp = outputNumber;
            while (temp % 5 == 0) {
                temp = temp / 5;
            }
            while (temp % 2 == 0) {
                temp = temp / 2;
            }
            while (temp % 3 == 0) {
                temp = temp / 3;
            }
            if (temp == 1) {
                i = i + 1;
            }
        }
        return outputNumber;
    }
}

this works for small numbers, but when the input is a big number, then I have Time Limit Exceeded The question is how to optimize this code?

Thank you!

Hint: You're looking for numbers of the form 2 a ×3 b ×5 c for non-negative integers a, b, c. Instead of looking for ugly numbers, wouldn't it be easier to just generate them?

I used two tricks to make it about twice as fast, but it's still far too slow. I suspect the check-all-integers-for-ugliness approach is hopeless, and you'll find faster approaches in the discussions on LeetCode.

class Solution {
    public int nthUglyNumber(int n) {
        for (int i = 1; true; i++)
            if (1418776204833984375L % (i / (i & -i)) == 0)
                if (--n == 0)
                    return i;
    }
}

The two tricks:

  • i & -i extracts the lowest 1-bit, so dividing by that takes out every factor 2.
  • 1418776204833984375 is 3 19 ×5 13 . Every positive int with only factors 3 and 5 divides that, and every other positive int doesn't.

I think the easiest way is to just maintain a collection of ugly numbers that we will need to visit. We start with a collection containing just 1 , and then at each step, we remove the lowest value, and add the values found by multiplying our lowest value by 2 , by 3 , and by 5 . Since these may be duplicates ( 24 = 8 * 3 and 24 = 12 * 2 ) but we only want them once apiece, our collection should be a Set.

My Java is far too rusty, but here's a JavaScript implementation that you could use as pseudocode:

 const ugly = (count) => { const upcoming = new Set ([1]) const found = [] while (found.length < count) { const next = Math.min (...upcoming.values ()) found.push (next) upcoming.delete (next) upcoming.add (2 * next) upcoming.add (3 * next) upcoming.add (5 * next) } return found } const uglies = ugly (1690) // This was the upper bound specified in the problem const nthUglyNumber = (n) => uglies [n - 1] console.log (nthUglyNumber (10)) console.log (nthUglyNumber (1690))

ugly finds the first count ugly numbers, returning them as an Array. Internally, it keeps two collections. upcoming is the ugly numbers we know we would eventually hit. found is an array of numbers we have actually reached, always taking the smallest of the upcoming values. When we select the next one, we remove it from upcoming and add to upcoming its 2 -, 3 -, and 5 -multiples.

This could be modified to be our only function. But it's nice to call it once for our top value, and store the resulting array, as uglies . Then nthUglyNumber is a simple function that extracts the value at the correct index.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM