青训营X豆包MarsCode 技术训练营刷题笔记 | 豆包MarsCode AI 刷题

83 阅读4分钟

一、题目解析:使数字和为偶数的组合计数

题目描述

给定若干数字组,从每个数字组中选择一个数字,组成一个新的数字。要求新数字的所有位数字之和为偶数,计算满足条件的所有组合数量。


题目分析

  • 问题本质

    1. 偶数判定规则:一个数的数字和为偶数,当且仅当参与加和的数字中,奇数的个数是偶数。
    2. 因此,问题可以转化为:在每个数字组中选择一个数字,确保组合后奇数的个数为偶数。
  • 核心挑战

    1. 从多个数字组中分别选择数字,选择的数字会影响最终的奇偶性。
    2. 对所有组合逐一检查虽然可行,但时间复杂度高;因此,需要利用算法优化。

算法思路

  1. 状态转移法
    将问题拆解为逐步选择的子问题,利用当前选择的数字,更新“奇数个数”的状态。可以通过递归或动态规划实现。

  2. 递归回溯法

    • 递归处理数字组,逐步选择数字。
    • 利用递归的层级管理不同数字组的选择,传递当前奇偶状态(奇数个数)。
    • 在递归出口处,判断奇数个数是否为偶数,如果满足条件,则计数。
  3. 关键优化

    • 不需要存储所有组合,只需累积当前状态,降低空间复杂度。
    • 可提前剪枝:当某路径下的奇数个数已不可能为偶数时,提前终止递归。

二、代码实现与详解

C++ 实现

#include <iostream>
#include <vector>
#include <string>
#include <functional>

// 计算满足条件的组合数量
int countEvenSums(const std::vector<std::string>& numbers) {
    int count = 0;  // 符合条件的组合数

    // 辅助函数:递归选择数字组中的元素并判断奇偶性
    std::function<void(int, int)> dfs = [&](int index, int oddCount) {
        // 递归出口:当遍历完所有数字组时,检查奇数个数是否为偶数
        if (index == numbers.size()) {
            if (oddCount % 2 == 0) {
                count++;
            }
            return;
        }

        // 遍历当前数字组的每个数字
        for (char digit : numbers[index]) {
            int isOdd = (digit - '0') % 2;  // 判断当前数字是否为奇数
            dfs(index + 1, oddCount + isOdd);  // 更新奇数个数并递归下一组
        }
    };

    dfs(0, 0);  // 从第0组开始,奇数个数初始为0
    return count;
}

// 测试用例
int main() {
    std::vector<std::string> numbers1 = {"123", "456", "789"};  // 示例输入1
    std::cout << countEvenSums(numbers1) << std::endl;  // 输出: 14

    std::vector<std::string> numbers2 = {"123456789"};  // 示例输入2
    std::cout << countEvenSums(numbers2) << std::endl;  // 输出: 4

    std::vector<std::string> numbers3 = {"14329", "7568"};  // 示例输入3
    std::cout << countEvenSums(numbers3) << std::endl;  // 输出: 10

    return 0;
}

代码详解

  1. 递归设计

    • 输入:当前处理的数字组索引 index 和当前奇数个数 oddCount
    • 递归逻辑:对于当前数字组的每个数字,更新奇数计数,并递归到下一组。
    • 递归出口:当遍历完所有数字组时,检查 oddCount 是否为偶数。
  2. 时间复杂度

    • 遍历所有组合:若数字组数量为 N,每组平均长度为 M,则时间复杂度为 O(N×M)O(N \times M)O(N×M)。
  3. 空间复杂度

    • 未存储所有组合,递归栈的深度为数字组的数量 NNN,因此空间复杂度为 O(N)O(N)O(N)。

三、知识总结:递归与问题分解的应用

递归核心要点

  1. 状态管理:递归时,需传递当前的状态,例如奇数个数。
  2. 出口条件:设计清晰的递归结束条件,避免无限递归。
  3. 优化递归:可以通过剪枝减少无效路径的计算。

递归常见场景

  • 组合问题:如本题、全排列、子集生成等。
  • 动态规划:递归通过记忆化实现优化。
  • 树形结构遍历:如二叉树的深度优先遍历。

四、学习计划:如何提升递归能力

分步掌握递归

  1. 从简单问题入手,例如计算斐波那契数列或阶乘。
  2. 学习结合状态转移的递归,如背包问题。
  3. 挑战更复杂的题目,例如全排列、括号生成等。

工具支持

  • 使用豆包MarsCode的代码调试功能,跟踪递归状态。
  • 配合书籍如《算法图解》深入学习。

五、豆包MarsCode AI 帮助刷题的亮点

  • 智能推荐题目:根据个人学习进度推送适合的练习。
  • 错题解析:精准分析错因,给出优化建议。
  • 代码提示:在卡住时提供实现思路和关键代码段。

六、总结

通过递归解决“数字和为偶数的组合计数”问题,不仅加深了对递归核心思想的理解,也提高了问题分解和状态管理的能力。未来将结合豆包MarsCode AI 的强大功能与经典资源,持续探索更深层次的算法问题,期待与你们分享更多学习成果!