二分数字组合
问题描述
小F面临一个有趣的挑战:给定一个数组,她需要将数组中的数字分为两组。分组的目标是使得一组数字的和的个位数等于给定的 A,另一组数字的和的个位数等于给定的 B。除此之外,还有一种特殊情况允许其中一组为空,但剩余数字和的个位数必须等于 A 或 B。小F需要计算所有可能的划分方式。
例如,对于数组 [1, 1, 1] 和目标 A = 1,B = 2,可行的划分包括三种:每个 1 单独作为一组,其余两个 1 形成另一组。如果 A = 3,B = 5,当所有数字加和的个位数为 3 或 5 时,可以有一组为非空,另一组为空。
测试样例
样例1:
输入:
n = 3,A = 1,B = 2,array_a = [1, 1, 1]
输出:3
样例2:
输入:
n = 3,A = 3,B = 5,array_a = [1, 1, 1]
输出:1
样例3:
输入:
n = 2,A = 1,B = 1,array_a = [1, 1]
输出:2
样例4:
输入:
n = 5,A = 3,B = 7,array_a = [2, 3, 5, 7, 9]
输出:0
题解:
这是一个子集划分问题,目标是找到所有满足条件的划分方法,使得两个子集的和的个位数分别等于目标 A 和 B。我们可以利用递归和动态规划等方法来解决。
思路解析
-
问题分解:
- 题目要求将数组分为两组,设这两组的和分别为 S1 和 S2。
- 要求 S1 的个位数等于 A,S2 的个位数等于 B,或者反之。
- 如果允许一组为空,则总和 S1 + S2 的个位数需要等于 A 或 B。
-
状态表示:
- 用递归或者动态规划来尝试不同的划分方法。
- 使用位运算可以表示某些数字是否属于当前子集,从而方便地遍历所有子集。
-
位运算枚举所有子集:
- 通过遍历 0 到 2n−12^n - 12n−1 的所有整数,可以枚举数组的所有子集。
- 对于每一个子集计算其和的个位数,并检查是否满足题目要求。
-
特殊情况:
- 如果一个子集为空,我们只需检查剩余子集的和的个位数是否等于 A 或 B。
代码实现
以下是使用位运算的解法,用于枚举所有可能的子集划分,并计算满足条件的划分数量。
public static int solution(int n, int A, int B, int[] array_a) {
int totalWays = 0;
int totalSum = 0;
// 计算数组总和
for (int num : array_a) {
totalSum += num;
}
// 枚举所有子集
for (int i = 0; i < (1 << n); i++) {
int sum1 = 0;
// 计算当前子集的和
for (int j = 0; j < n; j++) {
if ((i & (1 << j)) != 0) { // 如果第 j 位为 1,则该元素属于子集 1
sum1 += array_a[j];
}
}
int sum2 = totalSum - sum1; // 子集 2 的和
// 检查两组的个位数是否符合要求
if ((sum1 % 10 == A && sum2 % 10 == B)) {
totalWays++;
}
}
// 特殊情况:一组为空
if (totalSum % 10 == A || totalSum % 10 == B) {
totalWays++;
}
return totalWays;
}
public static void main(String[] args) {
// 测试用例
int[] array1 = {1, 1, 1};
int[] array2 = {1, 1, 1};
int[] array3 = {1, 1};
System.out.println(solution(3, 1, 2, array1) == 3);
System.out.println(solution(3, 3, 5, array2) == 1);
System.out.println(solution(2, 1, 1, array3) == 2);
}
代码说明
-
变量初始化:
totalSum用于计算数组的总和。totalWays记录满足条件的划分方式数量。
-
位运算枚举所有子集:
for (int i = 0; i < (1 << n); i++)用于枚举从 0 到 2n−12^n - 12n−1 的每个整数,每个整数的二进制位表示数组的一个子集。- 对于每个子集,计算该子集的和
sum1,剩余子集的和sum2 = totalSum - sum1。
-
条件检查:
- 检查
sum1和sum2的个位数是否等于 A 和 B 或 B 和 A。
- 检查
-
特殊情况:
- 如果允许一组为空,则检查总和
totalSum的个位数是否为 A 或 B。如果满足条件,计数增加一次。
- 如果允许一组为空,则检查总和
在 Java 中,1 << j 表示 将数字 1 左移 j 位。这是位移操作符的一种用法。其作用是在二进制表示中,将 1 向左移动 j 位,从而产生一个数,这个数在第 j 位是 1,其余位都是 0。例如:
1 << 0等于0001(二进制),即1。1 << 1等于0010(二进制),即2。1 << 2等于0100(二进制),即4。1 << 3等于1000(二进制),即8。
用于子集枚举的意义
在这个算法中,(i & (1 << j)) != 0 用来检查整数 i 的第 j 位是否为 1。这种操作通过位运算判断数组中的第 j 个元素是否应该被包含在当前子集中。
i是一个整数,用于表示子集的某种组合(例如,i = 5表示二进制101,即第 0 位和第 2 位为 1,对应选择数组中的第 0 个和第 2 个元素)。1 << j表示将 1 左移j位,从而构造一个仅在第j位上有 1 的数字。i & (1 << j)是一个按位与操作,用来检查i的第j位是否为 1。如果结果不为 0,则i的第j位是 1;否则是 0。
示例
假设 i = 5(二进制 101):
j = 0时,1 << 0是001,i & (1 << 0)结果是1(表示第 0 位为 1)。j = 1时,1 << 1是010,i & (1 << 1)结果是0(表示第 1 位为 0)。j = 2时,1 << 2是100,i & (1 << 2)结果是4(表示第 2 位为 1)。
这样,我们就可以通过位运算决定当前子集中是否包含数组的第 j 个元素。