欧拉筛、线性筛
方法:一个合数被分解为最小质因数*自然数,以最小质因数进行筛选,一旦是合数,且找到了最小质因子,那就停止往下找,以提高效率。
public class Main {
public static void main(String[] args) {
int n = 1000;
int[] prime = new int[n];
boolean[] isprime = new boolean[n];
int count = 0;
for (int i = 2; i <= n; i++) {
if (isprime[i] == false) {
prime[count++] = i;
}
for (int j = 0; j < count; j++) {
// 数目过大
if (i * prime[j] > n) {
break;
}
// 筛去合数
isprime[prime[j] * i] = true;
// 保证每个合数只会被它的最小质因数筛掉
if (i % prime[j] == 0) {
break;
}
}
}
}
}
欧拉函数
1和任何数都互质,欧拉函数中1的函数值=1
在欧拉筛的基础上实现欧拉函数
public class Main {
public static void main(String[] args) {
int n = 1000;
// 统计素数
int[] prime = new int[n];
boolean[] isprime = new boolean[n];
// 计算欧拉函数
int[] phi = new int[n];
int count = 0;
for (int i = 2; i <= n; i++) {
if (isprime[i] == false) {
prime[count++] = i;
// 与素数(质数)互质的数为:1 - (i-1) 共 i - 1 个
phi[i] = i - 1;
}
for (int j = 0; j < count; j++) {
// 数目过大
if (i * prime[j] > n) {
break;
}
// 筛去合数
isprime[prime[j] * i] = true;
// 保证每个合数只会被它的最小质因数筛掉
if (i % prime[j] == 0) {
// 说明prime[j]是 i 的最小质数(例如2x2:4,= 1 * 2 = 2个互质数)
phi[prime[j] * i] = phi[i] * prime[j];
break;
}
// 如果 i % prime[j] != 0 所以prime[j]是 i * prime[j] 的最小质数
phi[prime[j] * i] = phi[i] * (prime[j] - 1);
}
}
}
}
对于素数n而言,与其互质的数有:1 - n-1,也就是n - 1 - 1 + 1 = n - 1个,而非素数(也就是合数该如何处理?)
构造限制重复的字符串(中等)
贪心,从字典序最大的字母开始遍历,记录每次的连续长度即可。StringBuilder速度远远快于String的加减法,学会使用StringBuilder。
class Solution {
public String repeatLimitedString(String s, int repeatLimit) {
int[] cnt = new int[26];
for (int i = 0; i < s.length(); i++) {
cnt[s.charAt(i) - 'a']++;
}
StringBuilder ans = new StringBuilder();
int i = 25;
// len用于记录当前连续次数
int len = 0;
while (i >= 0) {
if (cnt[i] == 0) {
i--;
len = 0;
continue;
}
// 当前字符与字符串最后一个字符相同,且连续次数达到上限
if (len >= repeatLimit && ans.charAt(ans.length() - 1) == (char)(i + 97)) {
i--;
len = 0;
continue;
}
// 当前字符与字符串最后一个字符不相同,连续次数清零
if (ans.length() > 0 && ans.charAt(ans.length() - 1) != (char)(i + 97)) {
len = 0;
}
// 正常添加字符
while (cnt[i] > 0 && len < repeatLimit) {
ans.append((char)(i + 97));
len++;
cnt[i]--;
}
if (cnt[i] > 0) {
// 说明是因为超过连续次数限制导致的while循环结束
// 那就用更小的字母来填充,只用填1次就可以
for (int j = i - 1; j >= 0; j--) {
if (cnt[j] == 0) continue;
ans.append((char)(j + 97));
cnt[j]--;
break;
}
}
}
return ans.toString();
}
}
统计可以被k整除的下标对数目(困难)
可以固定nums[j],考虑nums[j]可能与k有公因数,所以我们只需要nums[i]为 k / gcd(nums[j],k) 的倍数即可。我们可以预处理所有数的可能因子数,然后再去遍历nums数组,固定其中一个数作为nums[j],去找 k / gcd(nums[j], k) 的倍数的个数,这个数必须是在j下标之前的数,不能等于j。
例如:4 * 9 = 36,k = 6,如果单看4,gcd(4, 6) = 2,k / 2 = 3,那么就只需要找3的倍数的个数。
static块只会在类生成对象是执行一次。
class Solution {
static int mx = 100001;
static LinkedList<Integer>[] divisor = new LinkedList[mx];
// 只初始化一次
static {
for (int i = 1; i < mx; i++) {
divisor[i] = new LinkedList<>();
}
// 找每个数的因子(包括自身)
for (int i = 1; i < mx; i++) {
for (int j = i; j < mx; j += i) {
// 注意是找因子数,不是找倍数
divisor[j].add(i);
}
}
}
public long countPairs(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
long ans = 0;
for (int i = 0; i < nums.length; i++) {
// 只需要去找 k / gcd(k, nums[i])的倍数的个数,范围是 i 之前已经遍历过的下标
ans += map.getOrDefault(k / gcd(k, nums[i]), 0);
// 看当前数nums[i]的因子有哪些,都存入map,方便后序使用
// 存入的这些因子数,代表 nums[i] 是这些因子数的倍数(这里要理解下),这样就可以实现倍数的统计了
for (int val : divisor[nums[i]]) {
map.put(val, map.getOrDefault(val, 0) + 1);
}
}
return ans;
}
public int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
}