取模运算的应用

取模运算的应用

LeetCode里遇到了很多求数量的题目,由于数量过于庞大,最终会要求返回

(int)(result % Long), 其中 Long代表一个比较大的数,比如 10^9 + 1

今天再次遇到了这种题目,是求:

(n! * m!) % q

其中 n m是 int , q是一个 long

勾起了我的兴趣,括号内 n! * m! 是极有可能超出Java中的Long取值范围的

题目

1175. 质数排列

提示,这道题划分为Easy是有争议的,划分为Easy完全是因为时间限制的很宽裕而已...

笔者的解法如下

import java.util.*;
class Solution {
    /* 100.00% 93.57% */
    final long MOD = 1000000007;
    public int numPrimeArrangements(int n) {
        // 首先 0 不是质数
        //  2 <= i <= n 质数的数量 n1
        // 题目转变为 
        // 质数的可选位置总共有: n1 个数字的全排列
        // n1! = n1 * (n1 - 1) * ... * 2 * 1
        // 其他数字的可选位置有: (n - n1)!
        // res = n1 * (n1 - 1)* * 2 * 1
        //         * (n - n1) * (n - n1 -1)*  * 2 * 1
        if (n == 1) return 1;
        boolean[] isPrim = new boolean[n + 1];
        Arrays.fill(isPrim, true);
        int sqrt_n = (int)Math.sqrt(n);
        for (int i = 2; i <= sqrt_n; i++) {
            if (isPrim[i]) {
                for (int j = i * i; j <= n; j += i) {
                    isPrim[j] = false;
                }
            }
        }
        int n1 = 0;
        for (int i = 2; i <= n; i++) {
            if (isPrim[i]) n1++;
        }
        return getAns(n, n1);
    }
    private int getAns(int n, int n1) {
        // 插一条 % 运算的分配率
        // (a * b) % p = (a % p * b % p) % p 
        int tmp = 0;
        long res = 0; // res = n1! * (n - n1)!
        long p = 1;
        if (n - n1 > n1) {
            // 先算 n1! 再算 (n - n1)!
            tmp = n1;
            while (tmp > 1) {
                p *= tmp;
                p %= MOD;
                tmp--;
            }
            res = (p * p) % MOD;
            for (int i = n1 + 1; i <= n - n1; i++) {
                res *= i;
                res %= MOD;
            }
        } else {
            // 先算 (n - n1)! 再算 n1!
            tmp = n - n1;
            while (tmp > 1) {
                p *= tmp;
                p %= MOD;
                tmp--;
            }
            res = (p * p) % MOD;
            for (int i = n - n1 + 1; i <= n1 ; i++) {
                res *= i;
                res %= MOD;
            }
        }
        return (int)(res % MOD);
    }
}

思路就是

  1. 求出 [1,n] 中质数的个数 n1
  2. 分析可得,最终排列组合的个数 = 质数的排列 * 合数的排列
    1. 求质数的排列 n1!
    2. 求合数的排列 (n - n1)!
    3. 求 res = n1! * (n - n1)!
    4. 按要求返回 (int)( (n1! * (n - n1)! ) % 1000000007 )
    5. 中间用了一点节约运算步骤的技巧 :
      1. 比较 n1 和 n - n1的较小值 m
      2. 由于最终的结果 ,有重叠部分 1 * 2 * 3 * ... * m
      3. n1! * (n - n1)! = (m!) * (m!) * (m + 1) * (m + 2) * ... * Math.max(n1, n - n1)

中间发现 n1! * (n - n1)! 无法用Long接收,导致溢出,因此直觉告诉我取模运算可以简化

查阅互联网得到取模运算的分配率公式

模(Mod)运算的运算法则、公式(不包括同余关系)

其中重要的点在于乘法运算的取模分配率

(a * b) % p=(a%p * b%p) %p

因此 (n1! * (n - n1)! ) % 1000000007 可以写成

(( n1! % 1000000007 ) * ( (n - n1)! % 1000000007 ) ) % 1000000007

进一步,由于阶乘本身就是乘法运算,因此每一步计算阶乘时,都可以 % 1000000007

最终的结果再 % 1000000007 即可,参考上文中的取模代码

tmp = n1;
while (tmp > 1) {
    p *= tmp;
    p %= MOD;// 根据分配率先取模
    tmp--;
}
res = (p * p) % MOD; // 根据分配率先取模
for (int i = n1 + 1; i <= n - n1; i++) {
    res *= i;
    res %= MOD;  // 根据分配率先取模
}
return (int)(res % MOD);// 根据分配率括号外取模
posted @ 2022-08-19 18:38  jentreywang  阅读(75)  评论(0编辑  收藏  举报