编程题-数字分组求偶数和

38 阅读6分钟

大家好,我是game1024,一名喜欢代码的测试同学,最近在做一些基础的编程题练手,希望通过将解题的过程记录下来,可以帮助自己熟悉一下c++语言各种操作

问题描述

小M面对一组从 1 到 9 的数字,这些数字被分成多个小组,并从每个小组中选择一个数字组成一个新的数。目标是使得这个新数的各位数字之和为偶数。任务是计算出有多少种不同的分组和选择方法可以达到这一目标。

  • numbers: 一个由多个整数字符串组成的列表,每个字符串可以视为一个数字组。小M需要从每个数字组中选择一个数字。

例如对于[123, 456, 789],14个符合条件的数为:147 149 158 167 169 248 257 259 268 347 349 358 367 369


测试样例

样例1:

输入:numbers = [123, 456, 789]
输出:14

样例2:

输入:numbers = [123456789]
输出:4

样例3:

输入:numbers = [14329, 7568]
输出:10

解题思路

整体思路

  1. 理解题目要求

    • 需要从每个数字组中选择一个数字,使得这些数字的各位数字之和为偶数。
    • 每个数字组是一个字符串,表示一组数字。
    • 比如:有三组数[123, 456, 789],从123里面选择1,从456里面选择4,从789里面选择7,组成新的数字是147, 1+4+7 = 12,是偶数,所以这是其中一种方案,总计所有满足需求的方案总数
  2. 分析数字的奇偶性

    • 一个数字的各位数字之和为偶数的条件是:所有选出的数字的各位数字之和为偶数。
    • 奇数 + 奇数 = 偶数
    • 偶数 + 偶数 = 偶数
    • 奇数 + 偶数 = 奇数
  3. 统计每个数字组中的奇数和偶数个数

    • 对于每个数字组,统计其中奇数和偶数的个数。
  4. 递归计算组合数

    • 使用递归的方式,先从一个数字组中选择数字,
      • 如果选择是奇数,那么再从剩余组里面选出和为奇数的方案,就能满足条件
      • 如果选择是偶数,那么再从剩余组里面选出和为偶数的方案,就能满足条件
      • 因为每次递归,问题规模都会下降,直到递归至最小规模时,问题变成求一组数字中的偶数个数,所以,该递归算法有效
  5. 返回最终结果

    • 最终结果是所有组合中各位数字之和为偶数的组合数。

1.分析一组数字中的奇偶个数

因为题目给的一组数字是int类型,所以我们需要先实现一个算法,遍历一个int类型当中,每一位的数字奇偶,方法就是用取余操作符%

  • 比如当一个数字(n % 10)的时候,结果得到的就是个位上的数字
  • 然后利用n=n/10缩减10倍,原来十位上的数字就变成了个位,继续(n % 10)就得到十位上的数字
  • 重复这个过程,我们就能扫描一个int类型当中,每一位的数字的奇数和偶数的数量了

为了方便,我这里定义的count函数返回两个值,一个是奇数的数量,一个是偶数的数量

  • count(number)[0] 为奇数的数量
  • count(number)[1] 为偶数的数量
// 判断是奇数
bool isJI(int num) { return num % 2; }

// 遍历int类型的每一位(按十进制遍历),返回其中的奇数 偶数的个数
tuple<int, int> count(int number) {

  int JI = 0, OU = 0;
  for (int i = 10; number > 0; number /= 10) {
    if (isJI(number % 10)) {
      JI++;
    } else {
      OU++;
    }
  }
  return std::make_tuple(JI, OU);
}

2.递归计算组合数

关于递归算法思路,先用张图简单释义一下数字的奇偶性

奇偶性.jpg

伪代码如下

// 先对两个函数定义
f(numbers, n) = 第n组(从0开始)开始选择,sum结果为奇数的方案数量
g(numbers, n) = 第n组(从0开始)开始选择,sum结果为偶数的方案数量 

// 奇数 = 奇数 + 偶数 or 偶数 + 奇数
f(numbers, n) = 第n组中的奇数数量 * g(numbers, n+1) + 第n组中的偶数数量 * f(numbers, n+1)=
// 偶数 = 奇数 + 奇数 or 偶数 + 偶数 
g(numbers, n) = 第n组中的偶数数量 * g(numbers, n+1) + 第n组中的奇数数量 * f(numbers, n+1)

这个函数的终止条件是 n = numbers.size() - 1

cpp代码,可以看到我并没有定义f(n),g(n)两个函数,我是直接使用std::tuple返回两个值,

  • solutionJiOu(numbers, start)[0] 是奇数的数量数量
  • solutionJiOu(numbers, start)[1] 是偶数的方案数量
tuple<int, int> solutionJiOu(const std::vector<int> &numbers, int start) {
  if (start == numbers.size() - 1) {
    return count(numbers[start]);
  }

  int Ji, Ou;
  std::tie(Ji, Ou) = count(numbers[start]);

  tuple<int, int> result = solutionJiOu(numbers, start + 1);
  int otherJi, otherOu;
  std::tie(otherJi, otherOu) = result;

  // 奇数 = 奇数 + 偶数 or 偶数 + 奇数
  int JiPossible = Ji * otherOu + Ou * otherJi;

  // 偶数 = 奇数 + 奇数 or 偶数 + 偶数
  int OuPossible = Ji * otherJi + Ou * otherOu;

  return std::make_pair(JiPossible, OuPossible);
}

3.返回最终结果

利用std::tie对元组进行解包,将偶数方案数量返回即可

int solution(std::vector<int> numbers) {
  // Please write your code here
  int Ji, Ou;
  std::tie(Ji, Ou) = solutionJiOu(numbers, 0);
  return Ou;
}

完整代码

递归实现

#include <iostream>
#include <tuple>
#include <vector>

using std::tuple;

// 判断是奇数
bool isJI(int num) { return num % 2; }

// 统计一个数字中,奇数 偶数的个数
tuple<int, int> count(int number) {

  int JI = 0, OU = 0;
  for (int i = 10; number > 0; number /= 10) {
    if (isJI(number % 10)) {
      JI++;
    } else {
      OU++;
    }
  }
  return std::make_tuple(JI, OU);
}

tuple<int, int> solutionJiOu(const std::vector<int> &numbers, int start) {
  if (start == numbers.size() - 1) {
    return count(numbers[start]);
  }

  int Ji, Ou;
  std::tie(Ji, Ou) = count(numbers[start]);

  tuple<int, int> result = solutionJiOu(numbers, start + 1);
  int otherJi, otherOu;
  std::tie(otherJi, otherOu) = result;

  // 奇数 = 奇数 + 偶数 or 偶数 + 奇数
  int JiPossible = Ji * otherOu + Ou * otherJi;

  // 偶数 = 奇数 + 奇数 or 偶数 + 偶数
  int OuPossible = Ji * otherJi + Ou * otherOu;

  return std::make_pair(JiPossible, OuPossible);
}

int solution(std::vector<int> numbers) {
  // Please write your code here
  int Ji, Ou;
  std::tie(Ji, Ou) = solutionJiOu(numbers, 0);
  return Ou;
}

int main() {
  // You can add more test cases here
  std::cout << (solution({123, 456, 789}) == 14) << std::endl;
  std::cout << (solution({123456789}) == 4) << std::endl;
  std::cout << (solution({14329, 7568}) == 10) << std::endl;
  return 0;
}

涉及的知识点有

  • 遍历数字的每一位:利用(n%10)遍历数字的每一位
  • 函数返回两个值
    • 元组:std::tuple
    • 解包:std::tie
  • 递归思想:当一个问题的规模可以不断下降时,就能使用