二分数字组合 | 豆包MarsCode AI刷题

153 阅读3分钟

分组问题的解决与分析

问题描述

给定一个数组,将其分为两组,使得两组数字的和的个位数分别等于目标值 A 和 B。此外,允许其中一组为空,但剩余数字和的个位数必须等于 A 或 B。我们需要计算满足条件的所有可能划分方式。

例如:

  • 数组 [1, 1, 1],目标 A = 1, B = 2:三种可能划分。
  • 数组 [1, 1, 1],目标 A = 3, B = 5:只有一种划分方式。

解题思路

1. 转化问题的核心

此问题的关键在于:

  1. 数字归约:所有数字仅影响和的个位数,因此可以将所有数组元素对 10 取模以简化计算。
  2. 总和验证:数组所有数字之和的个位数决定了划分的可行性。如果总和的个位数无法拆分为目标 A 和 B,则直接返回 0。
  3. 动态规划建模:通过状态转移的方式,枚举数组中每个数字是否放入某一组,从而统计满足条件的方案数。

2. 特殊情况

  • 一组为空:如果允许一组为空,则需要额外验证总和是否等于目标 A 或 B。
  • 无解情况:若总和的个位数无法被分解为 A 和 B 的和(模 10),则直接返回 0。

3. 动态规划设计

dp[i][j] 表示从前 i 个数字中选择若干数字,使它们的和的个位数为 j 的方案数。动态规划的状态转移方程如下:

  • dp[i][j] = dp[i-1][j](不选择第 i 个数字)。
  • dp[i][j] += dp[i-1][(j - array[i-1] + 10) % 10](选择第 i 个数字)。

最终结果为可以使得和的个位数为 B 的所有方案数。

4. 总体算法流程

  1. 将数组元素归约到个位数。
  2. 计算数组总和的个位数并进行初步判断。
  3. 使用动态规划枚举所有可能的分组情况,统计结果。

实现代码

以下是问题的完整 C++ 实现代码:

#include <iostream>
#include <vector>
#include <numeric>

using namespace std;

int solution(int n, int A, int B, vector<int>& array_a) {
    // 将元素规范化到 [0, 9] 的范围内
    for (int& x : array_a) {
        x %= 10;
    }
    
    int total_sum = accumulate(array_a.begin(), array_a.end(), 0) % 10;
    
    // 可以有一组为空,另一组为非空
    if (total_sum == A || total_sum == B) {
        return 1;
    }

    // 两个组的和的个位数不可能等于总和的个位数
    if (total_sum != (A + B) % 10) {
        return 0;
    }

    // dp[i][j] 表示前 i 个数中可以组成个位数为 j 的数量
    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]; // 不将第 i 个数字放入第一组
            dp[i][j] += dp[i - 1][(j - array_a[i - 1] + 10) % 10]; // 将第 i 个数字放入第一组
        }
    }

    return dp[n][B];
}

int main() {
    // 测试样例
    std::vector<int> array1 = {1, 1, 1};
    std::vector<int> array2 = {1, 1, 1};
    std::vector<int> array3 = {1, 1};

    std::cout << (solution(3, 1, 2, array1) == 3) << std::endl;
    std::cout << (solution(3, 3, 5, array2) == 1) << std::endl;
    std::cout << (solution(2, 1, 1, array3) == 2) << std::endl;

    return 0;
}

思考与总结

  1. 动态规划的优点:将全排列问题转化为状态转移问题,大大降低了时间复杂度,避免了直接枚举的指数爆炸。
  2. 边界条件的重要性:一组为空的特殊情况需要单独考虑,体现了算法设计的细致性。
  3. 扩展性:该算法适用于类似“模运算约束”的其他问题,例如分糖果或分配资源问题。

在实际应用中,动态规划的设计需要仔细分析状态的定义和转移方式,这样才能确保算法的正确性和效率。