数据结构中的排列组合 —— Java实现

Java中的排列组合

排列组合

这个概念高中已经学过了,排列(A)就是不看顺序,比如2,1和1,2算一种情况。而组合(C)就是看顺序,上述例子算两种情况。下面给出排列和组合的数学公式:

  • A (nm ) = n! / m!【排列】
  • C (nm) = n! / (m! * (n-m)!)【组合】

组合

杨辉三角预处理组合数

  • 杨辉三角的基本性质:
    第n行第m列的值等于组合数(n m)
    边界条件:(n0) = (nn) = 1
    递推关系:(nm) = (n-1m)+(n-1m-1)
  • 基础预处理
java 复制代码
// 初始化
int[][] C = new int[MAX][MAX];
for (int i = 0; i < MAX; i++) {
    C[i][0] = 1;
    C[i][i] = 1;
}

// 递推填充
for (int i = 2; i < MAX; i++) {
    for (int j = 1; j < i; j++) {
        C[i][j] = C[i-1][j] + C[i-1][j-1];
    }
}

例题1(FROM 洛谷 P2822)

代码实现

java 复制代码
import java.util.*;
public class Main{
    public static void main(String [] args){
        Scanner input = new Scanner (System.in);
        int t = input.nextInt();
        int k = input.nextInt();
        int MAX = 2001;//题没截取全,最后写了,数据最大会是2000,所以开2001
        int [][] c = new int [MAX+1][MAX+1];
        int [][] ans = new int [MAX+1][MAX+1];
        
//------------------------------------------------基础预处理------------------------------------------------------------------------------------------------------------------
        for(int i=0;i<MAX;i++){
            c[i][0] = 1 % k;//如果直接写1,当k=1时,1%1=0
            c[i][i] = 1 % k;
        }
        for(int i=2;i<=MAX;i++)
            for(int j=1;j<=i;j++)
                c[i][j] = (c[i-1][j] + c[i-1][j-1]) % k;
                
//---------------------------------------------------前缀和处理---------------------------------------------------------------------------------------------------------------
        int [][] rowPre = new int [MAX+1][MAX+1];
        for(int i=0;i<=MAX;i++)
            for(int j=0;j<=MAX;j++){
                rowPre[i][j] = (j == 0)?0:rowPre[i][j-1];
                if(j <= i && c[i][j] == 0)
                    rowPre[i][j] ++;
            }
//---------------------------------------------------答案数组处理---------------------------------------------------------------------------------------------------------------            
        for(int j = 0;j<=MAX;j++)
            for(int i=0;i<=MAX;i++){
                if(i == 0)
                    ans[i][j] = rowPre[i][j];
                else ans[i][j] = ans[i-1][j] + rowPre[i][j];
            }
//---------------------------------------------------输出处理------------------------------------------------------------------------------------------------------------------------------
        for(int i = 0;i<t;i++){
            int n = input.nextInt();
            int m = input.nextInt();
            System.out.println(ans[n][m]);
        }
    }
}

排列

在排列中,涉及到许多阶乘,阶乘的循环处理太过耗费时间,所以我们可以使用阶乘数组 来预处理。同时在计算排列数的时候,如果我们需要取模,就不可以单纯的使用除法,因为模运算中没有除法的分配律,这时候我们就要用到阶乘逆元数组。

阶乘数组

java 复制代码
static long [] fac = new long [MAX];
fac[0] = 1;//规定 0!= 1
for(int i=1;i<MAX;i++)
	fac[i] = fac[i-1] * i;

阶乘逆元数组

逆元类似于数学中的倒数,阶乘的逆元数组通常在模运算中使用,比如模数为7,则3 的逆元是 5,因为 3 * 5 = 15 ,15 % 7 = 1 ,如果存在b可以使(a*b)% mod = 1,则a与b互为逆元。

  • 小费马定理
    invFac[MAXN-1] = fac[MAXN-1]^(MOD-2) % MOD
    invfac[i] = (invfac[i+1]*(i+1))
java 复制代码
static long [] invfac = new long [MAX];
invfac[MAX-1] = pow(fac[MAX-1],MOD-2);//pow是自定义快速幂函数
for(int i=MAX-2;i>=0;i--)
	invfac[i] = (invfac[i+1]*(i+1)) % MOD;

例题1(FROM 洛谷 P4071)

有个问题非常棘手,由于Java的运行内存比C++大的多,所以我拼尽全力优化了所有也无法全部AC,总会有MLE。(AI也没招了......)大家如果有可以全部AC的方法可以发出来,我将逐字学习!

思路

在这道题中要求有多少种方式,我们从数学角度先将其建模。首先"满足恰好有m个位置使得 ai = i",所以我们要从n个数里取出m个数固定,这个取法有C(nm)种。然后我们排列 其他未固定的数字,值得注意的是:未固定数字不能再放在其原本的位置上 ,于是我们要用到错排。

  • 错排
    定义:n个数排列且所有数字都不在原有位置上的情况数
    初始化:D[0] = 1空排列视为合法错排,D[1] = 0只有一个元素无法错排
    递推公式:D(n) = (n-1) * ( D(n-1) + D(n-2) )

代码实现

java 复制代码
import java.util.*;
import java.io.*;

public class Main{
    static BufferedReader bf = new BufferedReader (new InputStreamReader (System.in));
    static StringTokenizer st;
    static int MOD = 1000000000 + 7;
    static int MAX = 1000000 + 1;
    static long [] fac = new long [MAX];
    static long [] invfac = new long [MAX];
    static long [] D = new long [MAX];
    public static void main(String [] args)throws IOException{
        init();
        int T = Integer.parseInt(next());
        StringBuilder sb = new StringBuilder();
        while(T>0){
            int n = Integer.parseInt(next());
            int m = Integer.parseInt(next());
            if(m>n){
                System.out.print(0);
                continue;
            }
            long c = C(n,m);
            long d = D[n-m];
            long ans = (c*d)%MOD;
            sb.append(ans).append("\n");
            T--;
        }
        System.out.print(sb.toString());
    }
    static void init(){
        fac[0] = 1;
        for(int i=1;i<MAX;i++)
            fac[i] = (fac[i-1] * i) % MOD;
        
        // 预处理阶乘逆元 - 使用费马小定理
        // invFac[MAXN-1] = fac[MAXN-1]^(MOD-2) % MOD
        invfac[MAX-1] = pow(fac[MAX-1],MOD-2);
        for(int i=MAX-2;i>=0;i--)
            invfac[i] = (invfac[i+1]*(i+1)) % MOD;

        D[0] = 1;
        D[1] = 0;
        for(int i=2;i<MAX;i++)
            D[i] = ((long)(i-1)*(D[i-1]+D[i-2]))%MOD;
    }
    static long C(int n,int m){
        if(m>n || m<0) return 0;
        return ((fac[n] * invfac[m])%MOD * invfac[n-m]) % MOD;
    }
    static long pow(long a,long b){
        long res = 1;
        while(b > 0){
            if( (b&1) == 1) {
                res *= a;
                res %= MOD;
            }
            a *= a;
            a %= MOD;
            b>>=1;
        }
        return res;
    }
    static String next() throws IOException{
        while(st == null||!st.hasMoreTokens()){
            String str = bf.readLine();
            if(str == null) return null;
            st = new StringTokenizer(str);
        }
        return st.nextToken();
    }
}
相关推荐
夏幻灵3 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码3 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
bkspiderx4 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
代码什么用5 小时前
Spring基础使用
java·后端·spring
程序员老陆5 小时前
深入理解 C++ thread_local:线程私有存储的正确打开方式
开发语言·c++·程序设计
沐晓时光5 小时前
C语言入门,深入理解指针(3)
c语言·开发语言
Su米苏5 小时前
Spring AI 中MCP 与普通 @Tool的区别
java·人工智能·spring
码云数智-园园5 小时前
unique_ptr 还是 shared_ptr?C++ 智能指针选型与内存泄漏实战分析
java·开发语言
老板一杯拿铁5 小时前
Java 异常体系详解:从字节码原理到最佳实践
java
蜗牛互联网5 小时前
Java 17 HttpClient调用文件转写API的超时与失败回退
java·人工智能·后端