问题描述
小M有 nn 张卡牌,每张卡牌的正反面分别写着不同的数字,正面是 aiai,背面是 bibi。小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,结果需要对 109+7109+7 取模。
例如:如果有3张卡牌,正反面数字分别为 (1,2),(2,3) 和 (3,2),你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
测试样例
样例1:
输入:
n = 3 ,a = [1, 2, 3] ,b = [2, 3, 2]
输出:3
样例2:
输入:
n = 4 ,a = [3, 1, 2, 4] ,b = [1, 2, 3, 1]
输出:6
样例3:
输入:
n = 5 ,a = [1, 2, 3, 4, 5] ,b = [1, 2, 3, 4, 5]
输出:32
public class Main {
private static int ans;//答案
private static int arrLen;//所有卡牌的数量
/**
*
* @param lastSum 前面已经算进去的卡牌数量的和
* @param sumCount 已经算进去的卡牌的数量
* @param a 剔除已经使用的正面卡牌
* @param b 剔除已经使用的反面卡牌
*/
public static void recallFunc(int lastSum,int sumCount,int[] a,int[] b)
{
int length=a.length;
//front是拿卡牌正面相加,back是拿反面相加
int front,back;
int[] newa,newb;
//每次进来就代表数组的长度大于0,接下来的操作是去一个数加进去,所以卡牌数量加1
sumCount++;
for(int i=0;i<length;i++){
//取卡牌的正反面,如果这个卡牌的正反面的数一样大怎么办,算一个还是两个?这里算两个
front=lastSum+a[i];
back=lastSum+b[i];
//length==1就代表已经将最后一个数加进去了,表示所有卡牌的情况
if(sumCount==arrLen){
if(front%3==0){
ans++;
}
if(back%3==0){
ans++;
}
if(ans>=1000000007){
ans-=1000000007;
}
return;
}
if(length-1-i==0){
return;
}
//创建新的数组
newa=new int[length-1-i];
newb=new int[length-1-i];
int index=i+1;
//TODO:创建的新数组的元素必须是之前没有用过的元素,否则会出现组合的重复
for(int j=0;index<length;index++){
newa[j]=a[index];
newb[j]=b[index];
j++;
}
recallFunc(front,sumCount,newa,newb);
recallFunc(back,sumCount,newa,newb);
}
}
public static int solution(int n, int[] a, int[] b) {
// write code here
ans=0;
arrLen=a.length;
recallFunc(0,0,a,b);
//System.out.println("总数是:"+ans);
return ans; // Placeholder return
}
public static void main(String[] args) {
System.out.println(solution(3, new int[]{1, 2, 3}, new int[]{2, 3, 2}) == 3);
System.out.println(solution(4, new int[]{3, 1, 2, 4}, new int[]{1, 2, 3, 1}) == 6);
System.out.println(solution(5, new int[]{1, 2, 3, 4, 5}, new int[]{1, 2, 3, 4, 5}) == 32);
}
}
代码分析: 这段Java代码解决了一个关于卡牌选择的问题,目的是找出所有可能的组合,使得所有向上的数字之和可以被3整除。以下是代码的详细分析:
- 类和方法定义:
Main类包含了主方法和辅助递归方法recallFunc。ans是一个静态变量,用于存储满足条件的组合数。arrLen是一个静态变量,表示卡牌的总数。
- 递归方法
recallFunc:- 参数
lastSum表示前面已经计算过的卡牌数字之和。 - 参数
sumCount表示已经计算过的卡牌数量。 - 参数
a和b分别表示卡牌正反面的数字数组,递归过程中会逐步缩小这两个数组。
- 参数
- 递归逻辑:
- 在每次递归调用中,
sumCount加1,表示处理下一张卡牌。 - 对于每张卡牌,计算选择正面和反面的和(
front和back)。 - 如果已经处理完所有卡牌(
sumCount == arrLen),检查front和back是否可以被3整除,并更新ans。 - 如果还有卡牌未处理,递归调用
recallFunc,分别传入front和back作为新的lastSum。 - 在递归调用之前,创建新的数组
newa和newb,这些数组不包含当前处理的卡牌,以避免重复计算。
- 在每次递归调用中,
代码总结:
-
递归与回溯:递归用于探索所有可能的组合,而回溯体现在每次递归调用后创建新的数组
newa和newb,这些数组排除了当前处理的卡牌,确保了每张卡牌只被选择一次。 -
边界条件:递归终止条件是当
sumCount等于arrLen,此时检查当前的和是否满足条件。 -
模运算:为了避免结果过大,代码中使用了模运算
ans %= 1000000007来保证结果在合理范围内。 存在的问题: -
性能问题:递归方法中的数组复制操作是性能瓶颈,因为它涉及到大量的内存操作。 改进建议: