题解:学而思编程 乘积最大

【题目来源】

学而思编程:乘积最大

【题目描述】

有一个正整数 \(n\),将 \(n\) 的每一位数码拆开,分成 \(3\) 组,将每组内的数重新排列成 \(3\) 个新的正整数。(可以不按原数的顺序排列)

\(3\) 个新的正整数的乘积最大可能是多少?

【输入】

\(1\) 个正整数 \(n\)

【输出】

输出最大乘积。

【输入样例】

123456

【输出样例】

136396

【核心思想】

  1. 问题分析:给定正整数 \(n\),将其各位数码拆开后分成 \(3\) 组,每组重新排列成一个新的正整数,要求这 \(3\) 个正整数的乘积最大。关键观察是:对于固定和的三个正整数,当它们尽可能接近时乘积最大(均值不等式)。因此策略是:将数码从大到小排序后,尽量让三个数的数值接近。

  2. 算法选择

    • 贪心分配:将排序后的数码循环分配到三个数中(大数码优先分配到当前最小的数)
    • 微调平衡:通过交换操作使三个数的数值更加接近,最大化乘积
  3. 关键步骤

    • 数码分解与排序:将 \(n\) 的每一位存入数组 \(a\),从大到小排序
    • 循环分配:对 \(i\)\(1\)\(n\):将 \(a[i]\) 分配到 \(v[(i-1) \bmod 3 + 1]\)
    • 补零处理
      • \(n \bmod 3 = 2\):给第三个数补一个 \(0\)(末尾加一个 \(0\) 位)
      • \(n \bmod 3 = 1\):给第二、三个数各补一个 \(0\)
    • 平衡调整
      • 让最大的数(\(v[1]\))尽可能小:若 \(v[1]\)\(v[3]\) 在某位相等,交换使 \(v[1]\) 更小
      • 让后两个数的差值尽可能小:根据已确定的大小关系,交换数码使两数更接近
    • 计算乘积:将三个向量转为整数,相乘后除以补零的影响因子
  4. 时间/空间复杂度

    • 时间复杂度:\(O(\log_{10} n)\),数码个数为 \(\log_{10} n\),排序和分配均为线性
    • 空间复杂度:\(O(\log_{10} n)\),存储数码和三个数的各位
  5. 贪心平衡分配的核心思想

    • 均值不等式指导:对于固定和 \(S = a + b + c\),乘积 \(abc\)\(a \approx b \approx c \approx S/3\) 时最大。因此分配策略的核心是让三个数的数值尽可能接近
    • 循环分配的初始策略:将大数码依次分配到三个数中,确保每个数都能获得较大的高位数码,避免某个数过大
    • 位数均衡:通过补零使三个数的位数相同(或接近),确保高位对齐比较和交换的有效性
    • 局部交换优化:在循环分配后,通过逐位比较和交换,进一步缩小三个数之间的差距。特别是让最大的数尽量小、后两个数的差尽量小,是贪心策略的精细化调整
    • 高位优先原则:交换操作从高位到低位进行,因为高位数码对数值大小的影响更大,优先调整高位能更有效地平衡三个数
    • 适用于数码分配、资源均衡、乘积最大化等组合优化问题

【解题思路】

【算法标签】

贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int a[20], n;  // 存储数字的每一位
int cnt, b[20], zero = 1;  // zero: 记录末尾0的个数
long long num;  // 输入的数字
vector<int> v[5];  // 三个数字的每一位,v[1],v[2],v[3]存储三个数

int main()
{
    cin >> num;  // 输入数字

    // 将数字分解为每一位
    while (num) {
        a[++n] = num % 10;  // 从低位到高位存储
        num /= 10;
    }

    // 从大到小排序
    sort(a + 1, a + 1 + n, greater<int>());

    // 将数字分配到三个数中(循环分配)
    for (int i = 1; i <= n; i++) {
        v[(i - 1) % 3 + 1].push_back(a[i]);  // 循环分配到v[1],v[2],v[3]
    }

    // 处理长度不是3的倍数的情况
    if (n % 3 == 2) {  // 长度除以3余2
        v[3].push_back(0);  // 第三个数补0
        zero = 10;  // 记录有一个末尾0
    }
    if (n % 3 == 1) {  // 长度除以3余1
        v[2].push_back(0);  // 第二个数补0
        v[3].push_back(0);  // 第三个数补0
        zero = 100;  // 记录有两个末尾0
    }

    bool f = 0;  // 标记是否需要调整
    bool f1 = 0;  // 标记第一个数的最高位是否需要调整

    // 检查第一个数的最高位和第三个数的最高位是否相等
    if (v[1][0] == v[3][0])
        f1 = 0;  // 相等,不需要调整
    else
        f1 = 1;  // 不相等,可能需要调整

    // 处理:让第一个最大的数尽可能小
    for (int i = 1; i < v[3].size(); i++) {
        // 检查前一位是否已经不同
        if (v[1][i - 1] != v[2][i - 1]) {
            f = 1;  // 已经不同
        }

        // 如果当前位v[1]和v[3]相等,继续
        if (v[1][i] == v[3][i]) {
            continue;
        } else {
            if (f1 == 0) {  // 如果最高位相等
                f1 = 1;  // 标记已处理
                continue;
            } else {
                swap(v[1][i], v[3][i]);  // 交换,让v[1]更小
            }
        }

        // 如果前一位相等且当前位不同,交换v[1]和v[2]
        if (v[1][i - 1] == v[2][i - 1] && v[1][i] != v[2][i] && f == 0) {
            swap(v[1][i], v[2][i]);
            f = 1;
        }
    }

    int t = -1;  // 标记哪个数更大:2表示v[2]大,3表示v[3]大

    // 让后两个数的差值尽可能小
    for (int i = 1; i < v[3].size(); i++) {
        if (t == -1) {  // 第一次确定大小关系
            if (v[2][i - 1] < v[3][i - 1]) {
                t = 3;  // v[3]更大
            } else if (v[2][i - 1] > v[3][i - 1]) {
                t = 2;  // v[2]更大
            }
        }
        if (t != -1) {  // 已经确定大小关系
            // 如果v[3]更大但当前位v[3]>v[2],交换让v[2]变大
            if (t == 3 && v[3][i] > v[2][i])
                swap(v[3][i], v[2][i]);
            // 如果v[2]更大但当前位v[3]<v[2],交换让v[3]变大
            else if (t == 2 && v[3][i] < v[2][i])
                swap(v[3][i], v[2][i]);
        }
    }

    long long x[5] = {0}, res = 1;  // 计算三个数
    for (int i = 1; i <= 3; i++) {
        for (int j = 0; j < v[i].size(); j++) {
            x[i] = x[i] * 10 + v[i][j];  // 构造数字
        }
        res *= x[i];  // 计算乘积
    }

    cout << res / zero;  // 除以补0的影响
    return 0;
}

【运行结果】

123456
136396
posted @ 2026-07-02 22:45  团爸讲算法  阅读(8)  评论(0)    收藏  举报