从零开始:一道“完美数”问题的详解 | 豆包MarsCode AI刷题

135 阅读3分钟

在这篇博客中,我们将探讨一道有趣的算法题:如何从一个数组中找到两个数的乘积为“完美数”的配对,并计算这样的配对数目。通过问题描述、思路剖析、代码实现及优化探讨,让你对这类题目有更加深入的理解。


问题描述

完美数的定义:一个数被称为“完美数”,当且仅当它的数位中只有一个非零数字。例如:

  • 是完美数:5000, 4, 1, 10, 200
  • 不是完美数:25, 123, 303

题目要求:给定一个大小为 n 的数组,从中选择两个元素,使它们的乘积是一个“完美数”。求这样的配对总数。


示例

样例 1
输入:arr = [25, 2, 1, 16]
输出:3
说明:三个配对分别是 (25, 2), (25, 1), (2, 1)

样例 2
输入:arr = [5, 50, 500, 5000]
输出:0
说明:数组中没有满足条件的配对。

样例 3
输入:arr = [2, 10, 100, 1000]
输出:6
说明:六个配对分别是 (2, 10), (2, 100), (2, 1000), (10, 100), (10, 1000), (100, 1000)


思路详解

1. 乘积是完美数的判定方法

要判断两个数的乘积是否是完美数,需要以下步骤:

  1. 计算乘积 product = a * b
  2. 统计 product 中的非零数字数量 nonZeroCount
  3. nonZeroCount == 1,则该乘积为完美数。

2. 暴力法求解

最直观的方法是对数组中的每一对数字 (i, j) 检查是否满足条件。遍历数组的所有组合,复杂度为 (O(n^2))。

代码实现如下:

public static int solution(int[] arr) {
    int count = 0;
    for (int i = 0; i < arr.length; i++) {
        for (int j = i + 1; j < arr.length; j++) {
            if (isPerfectProduct(arr[i], arr[j])) {
                count++;
            }
        }
    }
    return count;
}

public static boolean isPerfectProduct(int a, int b) {
    int product = a * b;
    int nonZeroCount = 0;
    while (product > 0) {
        if (product % 10 != 0) {
            nonZeroCount++;
        }
        product /= 10;
    }
    return nonZeroCount == 1;
}

图解思路

以样例 arr = [25, 2, 1, 16] 为例,画出配对过程:

  • 初始数组[25, 2, 1, 16]
  • 配对结果
    • (25, 2):乘积为 50,是完美数。
    • (25, 1):乘积为 25,不是完美数。
    • (25, 16):乘积为 400,是完美数。
    • (2, 1):乘积为 2,是完美数。
    • (2, 16):乘积为 32,不是完美数。
    • (1, 16):乘积为 16,是完美数。

最终共有 3 种有效配对。


时间复杂度分析

  1. 外层双重循环的复杂度为 (O(n^2))。
  2. 判断一个数是否为完美数的复杂度为 (O(\log_{10}k)),其中 (k) 是乘积的大小。

总时间复杂度约为 (O(n^2 \cdot \log_{10}k))。


优化思路

考虑到暴力解法的时间复杂度较高,我们可以尝试以下优化:

  1. 预处理数据:将所有数字转化为以其第一个非零数字为标识的形式。
    • 例如:5000 -> 5, 100 -> 1
  2. 利用字典存储信息:统计每个“关键数字”的出现次数,减少不必要的计算。

通过优化,可显著减少重复计算的开销,提升效率。


总结

这道题通过定义特殊数字及其相关性,考察了组合计算和逻辑判定的能力。我们采用了暴力法实现并给出了优化思路。关键在于对问题特性的深刻理解,结合数据预处理提升算法效率。

代码完整性和优化,是这类问题解法的核心。你也可以尝试实现上述优化,进一步挑战更大的数据规模!