问题描述
小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
Solution
思考发现,这个问题是可以划分成子问题的。具体来说,可以只关注其中一组,每当选定一个数字,会有加入这一组和加入另一组两种选择。我们可以遍历数字和的最后一位,也就是 0-10 ,这样,这两种选择就会对应到与原问题相同的子问题,同时,将这两个子问题的方案数加起来,就能够得到原问题的答案,那么就可以通过动态规划来解决这个问题。
使用 dp[i][j] 表示选择到第i个数字,数字和最后一位为 j 的方案数,那么就能得到状态转移方程
初始状态,不选择数字,数字和的最后一位是0是一种方案,所以 dp[0][0]=1
同时,数字的顺序在这里是无关紧要的,所以直接从前向后遍历即可
特殊情况判断
- 如果所有数字划分到一组,另一组为空,也就是 sum==A||sum==B 这时候只会有一种方案。因为 A 或 B 是非零的(我记得有这个条件,但是貌似更新过程中更新掉了?也可能是记错题目了,但是这样的确能过,所以应该是非零的)如果划分一些数字到空的那一组,那么非空的这一组必然不会满足数字和的最后一位的限制
- 如果两组都非空的话,首先可以判断 sum==(A+B)%10 这个条件,不满足的话不需要进行后面的计算,直接返回 0 即可
C++代码实现
int solution(int n, int A, int B, std::vector<int> arr) {
// Please write your code here
int sum = 0;
for(int i = 0; i < n; i++){
arr[i] %= 10;
sum += arr[i];
}
sum %= 10;
if(sum == A || sum == B) return 1;
if(sum != (A + B) % 10) return 0;
vector<vector<int>> dp(n+1, vector<int>(10, 0));
dp[0][0] = 1;
for(int i = 1; i <= n; i++){
for(int j = 0; j < 10; j++){
dp[i][j] += dp[i-1][j];
dp[i][j] += dp[i-1][(j - arr[i-1] + 10) % 10];
}
}
return dp[n][A];
}
时空复杂度
时间复杂度: O(n)
空间复杂度: O(n)
总结与思考
一开始比较容易的想到了动态规划,但是在怎么设计状态转移这里却卡住了。一开始想的是,使用 dp[i][A][B] 来表示选到第i个数字,一组的和为 A ,另一组的和为 B 的方案数,但是这样貌似是会导致重复计数的问题,但是想的不是很明白哪里重复,重复了多少。
最后问了一下 AI ,转变了思路,然后成功 AC ,不得不说在苦思不解的时候通过 AI 来打开思路还是可以的,现在面临 AI 时代,在学习中也可以多使用 AI 的帮助提升学习效率。