计算质数,丑数II,完全平方数,合并k个排序链表, 丑数,

总结

  • 统计数字n以内的质数,找到第n个丑数, 相似点

    • 都不是一个一个的检查,而是自底向上,从一个已知的数开始,质数的整数倍肯定不是质数,丑数的2,3,5倍还是丑数。
  • 统计数字n以内的质数,找到第n个丑数,组成n的完全平方数的最少个数,相似点

    • 自底向上
  • 找到第n个丑数,合并k个排序链表

    • 用堆找最小元素,
    • 弹出堆顶最小元素,以该元素为基,再去得到小元素,加入到堆中

204. 计数质数 简单

统计所有小于非负整数 n 的质数的数量。

示例:

输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。

思路

参考:
作者:labuladong
链接:https://leetcode-cn.com/problems/count-primes/solution/ru-he-gao-xiao-pan-ding-shai-xuan-su-shu-by-labula/
来源:力扣(LeetCode)

  • 最容易想到的:
class Solution {
    public int countPrimes(int n) { 

        int res = 0;
        for(int i = 2; i < n; i++) {
            if(isPrime(i)) res++;
        }
        
        return res;
    }

    private boolean isPrime(int num) {
        for(int i = 2; i < num; i++) {
            if(num % i == 0) return false;
        }

        return true;
    }
}
  • 优化,找重复计算项,去除重复计算项
    • 判断n是不是素数
    • 1.不需要n去除[2, n-1],判断是否可以整除, 而是n去除[2, sqrt(n)]即可。
      12 = 2 × 6
      12 = 3 × 4
      12 = sqrt(12) × sqrt(12)
      12 = 4 × 3
      12 = 6 × 2
      • for 循环只需要遍历 [2,sqrt(n)] 就够了

public int countPrimes(int n) {
   int count = 0;
   for (int i = 1; i < n; i++) {
      if (isPrime(i)) count++;
   }
   return count;
}

private boolean isPrime(int num) {
   if (num <= 1) return false;
   // Loop's ending condition is i * i <= num instead of i <= sqrt(num)
   // to avoid repeatedly calling an expensive function sqrt().
   for (int i = 2; i * i <= num; i++) {
      if (num % i == 0) return false;
   }
   return true;
}
  • 2.从 2 开始,我们知道 2 是一个素数,那么 2 × 2 = 4, 3 × 2 = 6, 4 × 2 = 8… 都不可能是素数了。
    • 发现 3 也是素数,那么 3 × 2 = 6, 3 × 3 = 9, 3 × 4 = 12… 也都不可能是素数了
int countPrimes(int n) {
    boolean[] isPrim = new boolean[n];
    // 将数组都初始化为 true
    Arrays.fill(isPrim, true);

    for (int i = 2; i < n; i++) 
        if (isPrim[i]) 
            // i 的倍数不可能是素数了
            for (int j = 2 * i; j < n; j += i) 
                    isPrim[j] = false;
    
    int count = 0;
    for (int i = 2; i < n; i++)
        if (isPrim[i]) count++;
    
    return
  • 3.很难注意到内层的 for 循环也可以优化,我们之前的做法是:
    • for (int j = 2 * i; j < n; j += i)
      isPrim[j] = false;

    • 可以把 i 的整数倍都标记为 false,但是仍然存在计算冗余。

    • 比如 n = 25,i = 4 时算法会标记 4 × 2 = 8,4 × 3 = 12 等等数字,但是这两个数字已经被 i = 2 和 i = 3 的 2 × 4 和 3 × 4 标记了。

    • 我们可以稍微优化一下,让 j 从 i 的平方开始遍历,即规定每个数从大于等于i的i倍开始,而不是从 2 * i 开始:

    • 例如: 5 × 2 = 10 , 5的2倍,也是2的5倍; 5 × 3 = 15,5的3倍,也是3的6倍,所以从5的5倍开始,不要前面重复的。

    • for (int j = i * i; j < n; j += i)
      isPrim[j] = false;

      // 有点dp的感觉
      // 先让boolean[] isPrime = new boolean[] 数组全部是true
      // 只要是质数,那么他的整数倍就一定不是质数
      // 优化1:只需要判断 [2 ~ sqrt(n)]
      // 优化2:整数倍判断会有重复,从 j = i * i 判断

int countPrimes(int n) {
    boolean[] isPrim = new boolean[n];
    Arrays.fill(isPrim, true);
    for (int i = 2; i * i < n; i++) 
        if (isPrim[i]) 
            for (int j = i * i; j < n; j += i) 
                isPrim[j] = false;
    
    int count = 0;
    for (int i = 2; i < n; i++)
        if (isPrim[i]) count++;
    
    return count;
}
  • 面试时,面试官要我把cout写在上面的两层for()循环里;此时,for()循环里,就不能是i * i < n了,而是i < n;
    • 因为要统计小于n的所有质数
    • i * i < n的作用:
    • 例如:
    • 12 = 2 × 6
      12 = 3 × 4
      12 = sqrt(12) × sqrt(12)
      12 = 4 × 3
      12 = 6 × 2
      统计质数2的6倍

如果写成i * i < n,结果乘以2倍,出现以下错误:因为 2 * 2 < 3, 不会进入循环,结果为0;但其实结果为1;

  • 针对一些元素,不行;
    在这里插入图片描述

264. 丑数 II

编写一个程序,找出第 n 个丑数。

丑数就是质因数只包含 2, 3, 5 的正整数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

思路

  • 暴力
    • 从1开始判断是否是丑数,是则计数加1;不是则继续向后面的数判断。
    • 其实判断丑数的代码,还挺值得多思考的,
class Solution {
    public int nthUglyNumber(int n) {
        // 从1开始判断,到第n个为止
        int count = 1;
        int i = 1;
        while(count <= n) {
            if(isUglyNumber(i)) {
                count++;
            }
            i++;
        }

        return i - 1; // 第n个数之后,i还会自增一次,所以要减1
    }

    private boolean isUglyNumber(int num) {
        // 不断去除2,3,5,剩下的余数是1则是丑数,不是1则不是丑数
		
        int[] factors = new int[]{2, 3, 5};
        for(int factor : factors) {
            while(num % factor == 0) {
                num = num / factor;
            }
        }

        return num == 1;

    }
}
  • 自底向上

    • 和从2开始向后寻找质数一样,质数的整数倍一定不是质数。
    • 从1开始寻找丑数,根据丑数 = 2^i * 3^j * 5^k, 丑数的2倍,3倍,5倍一定是丑数。
      • 寻找到的丑数作为基,继续乘以2,3,5向后寻找,但由于是要找到第n个,所以要从小到大的依次寻找丑数。排序,放入堆中自动排序。
      • 还有一点,因为依次寻找数的整数倍,寻找的整数之间肯定会有重复,期间还要去重。怎么去重:每次添加元素时,和堆中的peek()值比较,如果相等则,不添加?
      • 堆中弹出的第n个就是第n个丑数。
  • 要用Long,不然会报错

class Solution {
    public int nthUglyNumber(int n) {
        // 一个一个判断找丑数太耗时,因为很多数不是丑数
        // 类似dp思想,自底向上;和寻找质数的思想,质数的整数倍肯定不是质数,丑数的2,3,5倍肯定是丑数
        // 关键: 从小到大,要找第n个丑数,找第n个,就想到堆
        // 类似于合并k个链表思想:将最小的元素添加进去,再弹出最小的元素,以该元素作为基底,再去乘以2,3,5作为新的丑数
        // 添加到堆中,再弹出第二小的丑数,再以他为基础...
        // 即弹出的最小的,以他为基础的,肯定是第二小的
        // 这里还多涉及一个去重问题, 整数倍的整数之间肯定存在重复,导致弹出第i个最小堆顶元素后,此时本是第i+1个最小元素,但是是重复的

        Queue<Long> queue = new PriorityQueue<Long>();
        int count = 0;
        long uglyNum = 1;
        queue.add(uglyNum);
        int[] factors = new int[]{2, 3, 5};
        while(count < n) {
            if(queue.size() > 0) {
                uglyNum = queue.poll();
                count++;
            }
            while(queue.size() > 0 && queue.peek() == uglyNum) {
                queue.poll();
            }
            for(int factor : factors) {
                queue.add(factor * uglyNum);
            }
        }
        return (int) uglyNum;
    }
}
  • 用TreeSet去重排序
class Solution {
    public int nthUglyNumber(int n) {
        // 自底向上
        // 堆排序
        // 去重

        TreeSet<Long> set = new TreeSet<Long>();
        long uglyNum = 1;
        set.add(uglyNum);
        int count = 0;
        int[] factors = new int[]{2, 3, 5};

        while(count < n && set.size() > 0) {
            uglyNum = set.pollFirst();
            count++;
            for(int factor : factors) {
                set.add(factor * uglyNum);
            }
        }
        return (int) uglyNum;
    }
}

279. 完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

示例 1:

输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9.

思路

  • int[] dp 组成n的完全平方数个数
  • dp[i] = Math.min(dp[i - j*j] + 1, dp[i]);
  • 自底向上
class Solution {
    public int numSquares(int n) {

        int[] dp = new int[n+1];
        for(int i = 1; i <= n; i++) {
            dp[i] = i;
        }

        for(int i = 1; i <= n; i++) {
            for(int j = 1; i - j*j >= 0; j++) {
                dp[i] = Math.min(dp[i - j*j] + 1, dp[i]);
            }
        }

        return dp[n];

    }
}

23. 合并K个排序链表

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

示例:

输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6

思路

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        // 堆来排序
        // 每次弹出最小的元素,依次next

        Queue<ListNode> queue = new PriorityQueue<>((n1, n2) -> n1.val - n2.val);
        int k = lists.length;
        for(int i = 0; i < k; i++) {
            if(lists[i] != null) {
                queue.add(lists[i]);
            }
        }

        ListNode prev = new ListNode(-1);
        ListNode curr = prev;
        
        while(queue.size() > 0) {
            ListNode minNode = queue.poll();
            curr.next = minNode;
            curr = curr.next;
            
            if(minNode.next != null) {
                queue.add(minNode.next);
            }
        }
        return prev.next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值