剑指 Offer 49. 丑数--暴力搜索、小顶堆、动态规划

1.题目

2.思路

这道题二刷还是忘记了最佳方法是动态规划,最小堆也没想起来,只会暴力搜索(两种思路的暴力)。值得反思!!!

1.超时--O(2 ^32)

第一个想法是,遍历所有的数字,然后判断这个数是不是丑数。大概范围是从【0, 2^31 - 1]查找。这样的时间复杂度是O(2 ^32).大于 4 * 10^9.肯定超时!超时超时超时!!!

2.勉强--O(n * n)

第二个想法是,先找第一个丑数1,再找第二个丑数2,再找第三个丑数3,依次挨个找下去。每一个丑数肯定来源于前面已经出现的丑数。所以挨个遍历已经出现的丑数可以得到答案,时间复杂度为O(n * n) == 1690 * 1690.勉强不超时。800ms....然后肯定能优化的,想不起来了。。。

 3.小顶堆--O(nlogn)

第三个思路是,用小顶堆来解决。当x是丑数时,那么2 * x, 3 * x, 5 * x也必然是丑数,加入优先队列即可。注意可能有重复的值,所以加一个哈希表或者set去重,时间复杂度为O(n * (logn + log3n) ).堆弹出堆顶元素,logn,每次弹出一个元素, 都有可能加入3个元素,所以最终堆里面可能有3 * n个元素,所以是log3n.而不是3 *logn.所以最终时间复杂度O(nlogn)

class Solution {
    public int nthUglyNumber(int n) {
        int ans = 0;
        if(n == 1)return n;
        //最小堆。
        // x 是丑数,2x, 3x, 5x也是丑数
        Queue<Long>q = new PriorityQueue();
        Map<Long, Integer>map = new HashMap();
        q.add((long)1);
        int cnt = 0 ;
        int[]arrs = new int[]{2, 3, 5};
        while(!q.isEmpty()){
            long cur = q.poll();
            cnt ++;
            if(cnt == n) return (int)cur;
            for(int arr : arrs){
                if(map.getOrDefault(arr * cur, -1) != -1)
                    continue;
                else{
                    q.add(arr * cur);
                    map.put(arr * cur, 1);
                }
                    
            }

        }

        return -1;

    }
}

4.动态规划--O(n)

第四个思路,更加巧妙!!一维的动态规划!dp[i] 定义为第i个丑数为dp[i].那么状态转移函数应该怎么写呢?巧妙的是定义了三个指针p2, p3, p5,分别指向上一个丑数的位置。一开始都指向1.

dp[i] = Math.min(dp[p2] * 2, dp[p3] * 3, dp[p5] * 5).p2, p3, p5,代表每次使用2, 3, 5最后的丑数位置。如果dp[i] == dp[p2] * 2,那么说明下一个丑数是用了2,所以p2++.同理p3, p5.

这个的基础还是来源于下一个丑数必然来源于上一个丑数 * {2, 3, 5}中的一个。用三个指针p2, p3, p5减少了重复数据的计算,保证了每次得到的丑数都是最小且递增的。所以跟小顶堆相比减少了从堆里查找最小的丑数的时间,所以时间从O(nlogn) 到O(n).

class Solution {
    public int nthUglyNumber(int n) {
        int ans = 0;
        if(n == 1)return n;
        
        // 动态规划
        int p2 = 1, p3 = 1, p5 = 1;
        int[]dp = new int[n + 1]; // dp[i]表示第i个丑数
        dp[1] = 1;

        for(int i = 2 ; i < n + 1 ; i++){
            // 核心
            dp[i] = Math.min(Math.min(dp[p2] * 2, dp[p3] * 3), dp[p5] * 5);
            if(dp[i] == dp[p2] * 2)
                p2++;
            if(dp[i] == dp[p3] * 3)
                p3++;
            if(dp[i] == dp[p5] * 5)
                p5++;

        }
        return dp[n];

    }
}

3.结果

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值