【算法练习】日常刷题记录(线性筛、欧拉函数、LeetCode题)

欧拉筛、线性筛

方法:一个合数被分解为最小质因数*自然数,以最小质因数进行筛选,一旦是合数,且找到了最小质因子,那就停止往下找,以提高效率。

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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@u@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值