学习方法与心得:动态规划求方案数
/**
* 小M有n张卡牌,
* 每张卡牌的正反面分别写着不同的数字,
* 正面是ai,背面是bi,
* 小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。
* 你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,
* 结果需要对10^9+7 取模。
* 例如:如果有3张卡牌,
* 正反面数字分别为 (1,2),(2,3) 和 (3,2),
* 你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
*/
1. 解题背景
在解决豆包 MarsCode AI 刷题题库中的卡牌反面求和问题的时候,我最开始的想法是用回溯法求出所有的组合方式,再从所有的组合方式中找到可以被整除的方式。这个方法过于暴力,和预料的一样提交超时了。我只好求助于豆包 MarsCode AI。在Ai的帮助下,我学会了使用动态规划的方法来讨论这个问题,时间复杂度一下从o(2^N)降到了o(n^3)。从这道题中,我学到了如何建立递推方程,以及如和更新DP数组。
package JuejinAiCodes;
public class CardAdd {
/**
* 小M有n张卡牌,
* 每张卡牌的正反面分别写着不同的数字,
* 正面是ai,背面是bi,
* 小M希望通过选择每张卡牌的一面,使得所有向上的数字之和可以被3整除。
* 你需要告诉小M,一共有多少种不同的方案可以满足这个条件。由于可能的方案数量过大,
* 结果需要对10^9+7 取模。
* 例如:如果有3张卡牌,
* 正反面数字分别为 (1,2),(2,3) 和 (3,2),
* 你需要找到所有满足这3张卡牌正面或背面朝上的数字之和可以被3整除的组合数。
*/
private static final int MOD = 1000000007;
public static int solution(int n, int[] a, int[] b) {
// DP数组,dp[i][j]表示前i张卡牌的组合,余数为j(j = 0, 1, 2)时的方案数
int[][] dp = new int[n + 1][3];
// 初始状态:前0张卡牌,只有一种方案,且和为0(即余数为0)
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
int x = a[i - 1] % 3; // 卡牌正面的余数
int y = b[i - 1] % 3; // 卡牌反面的余数
for (int j = 0; j < 3; j++) {
// 选择正面或反面,更新余数的方案数
dp[i][j] = (dp[i - 1][(j - x + 3) % 3] + dp[i - 1][(j - y + 3) % 3]) % MOD;
}
}
// 返回余数为0的方案数
return dp[n][0];
}
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);
}
}
2. 解题思路
-
整除性与余数
- 数字能否被3整除,完全取决于它对3的余数。
- 每张卡牌的正反面数字对3取余后,只有三种可能:
0、1、2。
-
动态规划定义
- 用
dp[i][j]表示前i张卡牌中,选出的数字之和余数为j的方案数(j = 0, 1, 2)。
- 用
-
转移关系
-
当加入第
i张卡牌时,可以选择正面或反面:- 如果选正面,余数变为
(j - x + 3) % 3,其中x是正面的余数。 - 如果选反面,余数变为
(j - y + 3) % 3,其中y是反面的余数。
- 如果选正面,余数变为
-
状态转移方程为:
java 复制代码 dp[i][j] = dp[i-1][(j-x+3)%3] + dp[i-1][(j-y+3)%3];
-
-
初始状态
dp[0][0] = 1:前0张卡牌,和为0的唯一方案。dp[0][1] = dp[0][2] = 0:前0张卡牌和为1或2无方案。
-
最终结果
dp[n][0]表示前n张卡牌中,数字之和能被3整除的总方案数。
3. 复杂度分析
-
时间复杂度
O(n * 3):每张卡牌有3种余数,状态转移需要遍历所有卡牌。- 因此,时间复杂度为
O(n)。
-
空间复杂度
O(n * 3):需要一个二维数组dp存储n张卡牌的3种余数状态。- 若使用滚动数组优化,空间复杂度可以降为
O(3)。