算法基础篇 · 02 高精度(C++ 题解)

算法基础篇 · 02 高精度(C++ 题解)

一句话思想:当数据的值特别大、各种类型都存不下时,用字符串读入、数组存储每一位,再用代码模拟小学列竖式的计算过程。

适用场景:超大整数(可达 10⁵⁰⁰ 甚至更长)的加减乘除运算。

高精度算法本质上还是模拟算法:用代码模拟小学列竖式计算加减乘除的过程。

高精度通用套路

  1. 用字符串读入数据
  2. 将字符串的每一位拆分,逆序放在数组中(个位对齐,方便进位 / 借位);
  3. 利用数组模拟列竖式的运算过程(对应位运算 + 进位 / 借位);
  4. 处理结果的位数(进位导致的变长、前导零导致的变短)。

本章目录

题目 来源 难度
2.1 高精度加法 洛谷 P1601
2.2 高精度减法 洛谷 P2142
2.3 高精度乘法 洛谷 P1303
2.4 高精度除法 洛谷 P1480

2.1 高精度加法

题目描述

高精度加法,相当于 a + b problem,不用考虑负数。

输入描述

  • 分两行输入。
  • a, b ≤ 10⁵⁰⁰。

输出描述

输出只有一行,代表 a + b 的值。

示例

示例一

复制代码
输入:
1
1

输出:
2

示例三

复制代码
输入:
1001
9099

输出:
10100

解题思路

模拟小学「列竖式」计算「两数相加」的过程

  1. 用字符串读入数据;
  2. 将字符串的每一位拆分,逆序放在数组中;
  3. 模拟列竖式计算的过程:
    • a. 对应位累加(还要加上低位的进位);
    • b. 处理进位(c[i] / 10 加到下一位);
    • c. 处理余数(c[i] %= 10 保留个位);
  4. 处理结果的位数:最高位如果产生新的进位,结果位数 +1。

C++ 参考代码

cpp 复制代码
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e6 + 10;

int a[N], b[N], c[N];
int la, lb, lc;

// 高精度加法的模板 - c = a + b
void add(int c[], int a[], int b[])
{
    for(int i = 0; i < lc; i++)
    {
        c[i] += a[i] + b[i];  // 对应位相加,再加上进位
        c[i + 1] += c[i] / 10; // 处理进位
        c[i] %= 10;            // 处理余数
    }
    if(c[lc]) lc++; // 最高位产生进位,位数 +1
}

int main()
{
    string x, y; cin >> x >> y;

    // 1. 拆分每一位,逆序放在数组中
    la = x.size(); lb = y.size(); lc = max(la, lb);
    for(int i = 0; i < la; i++) a[la - 1 - i] = x[i] - '0';
    for(int i = 0; i < lb; i++) b[lb - 1 - i] = y[i] - '0';

    // 2. 模拟加法的过程
    add(c, a, b); // c = a + b

    // 输出结果
    for(int i = lc - 1; i >= 0; i--) cout << c[i];
    return 0;
}

复杂度分析

  • 时间复杂度:O(max(la, lb)),只需按位扫描一遍。
  • 空间复杂度:O(la + lb),存储两个加数与结果数组。

易错点

  1. 数组要逆序存储,保证个位对齐;
  2. 处理完所有位后要判断最高位是否产生新的进位(if(c[lc]) lc++);
  3. 输出时从最高位 lc - 1 逆序输出。

2.2 高精度减法

题目描述

高精度减法。

输入描述

两个整数 a, b(第二个可能比第一个大)。

输出描述

结果(是负数要输出负号)。

示例

复制代码
输入:
2
1

输出:
1

解题思路

模拟小学「列竖式」计算「两数相减」的过程

  1. 用字符串读入数据;
  2. 判断两个数的大小,让较大的数在前 。注意字典序 vs 数的大小:
    • a. 位数相等:按字典序比较;
    • b. 位数不等:按照字符串的长度比较;
  3. 将字符串的每一位拆分,逆序放在数组中;
  4. 模拟列竖式计算的过程:
    • a. 对应位求差;
    • b. 处理借位(c[i] < 0 时向高位借 1);
  5. 处理前导零。

C++ 参考代码

cpp 复制代码
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e6 + 10;

int a[N], b[N], c[N];
int la, lb, lc;

// 比大小:x < y 返回 true
bool cmp(string& x, string& y)
{
    // 先比较长度
    if(x.size() != y.size()) return x.size() < y.size();
    // 再按照 字典序 的方式比较
    return x < y;
}

// 高精度减法的模板 - c = a - b
void sub(int c[], int a[], int b[])
{
    for(int i = 0; i < lc; i++)
    {
        c[i] += a[i] - b[i]; // 对应位相减,然后处理借位
        if(c[i] < 0)
        {
            c[i + 1] -= 1; // 借位
            c[i] += 10;
        }
    }
    // 处理前导零
    while(lc > 1 && c[lc - 1] == 0) lc--;
}

int main()
{
    string x, y; cin >> x >> y;

    // 比大小:让较大的数在前
    if(cmp(x, y))
    {
        swap(x, y);
        cout << '-';
    }

    // 1. 拆分每一位,然后逆序放在数组中
    la = x.size(); lb = y.size(); lc = max(la, lb);
    for(int i = 0; i < la; i++) a[la - i - 1] = x[i] - '0';
    for(int i = 0; i < lb; i++) b[lb - i - 1] = y[i] - '0';

    // 2. 模拟减法的过程
    sub(c, a, b); // c = a - b

    // 输出结果
    for(int i = lc - 1; i >= 0; i--) cout << c[i];
    return 0;
}

复杂度分析

  • 时间复杂度:O(max(la, lb)),按位扫描一遍。
  • 空间复杂度:O(la + lb),存储两个数与结果数组。

易错点

  1. 比大小时先比位数、位数相同再比字典序,不能直接比较字符串;
  2. 被减数小于减数时要交换并输出负号;
  3. 处理完要去掉前导零while(lc > 1 && c[lc - 1] == 0) lc--),但要保留至少 1 位。

2.3 高精度乘法

题目描述

给出两个非负整数,求它们的乘积。

输入描述

输入共两行,每行一个非负整数。

输出描述

输出一个非负整数表示乘积。

示例

复制代码
输入:
1
2

输出:
2

解题思路

无进位相乘再相加:还是「列竖式」,但是每一位相乘的时候不考虑进位,直接把乘的结果放在对应位上;等到所有对应位置「乘完」并且「累加完」之后,再「统一处理进位」。

以 123 × 456 为例:竖式中第 i 位乘第 j 位的结果放在第 i + j 位上,全部累加完毕后统一进位。

  1. 用字符串读入数据;
  2. 将字符串的每一位拆分,逆序放在数组中;
  3. 模拟无进位相乘再相加的过程(c[i + j] += a[i] * b[j]);
  4. 统一处理进位、处理前导零。

C++ 参考代码

cpp 复制代码
#include <iostream>

using namespace std;

const int N = 1e6 + 10;

int a[N], b[N], c[N];
int la, lb, lc;

// 高精度乘法的模板 - c = a * b
void mul(int c[], int a[], int b[])
{
    // 无进位相乘,然后相加
    for(int i = 0; i < la; i++)
    {
        for(int j = 0; j < lb; j++)
        {
            c[i + j] += a[i] * b[j];
        }
    }

    // 处理进位
    for(int i = 0; i < lc; i++)
    {
        c[i + 1] += c[i] / 10;
        c[i] %= 10;
    }

    // 处理前导零
    while(lc > 1 && c[lc - 1] == 0) lc--;
}

int main()
{
    string x, y; cin >> x >> y;

    // 1. 拆分每一位,逆序放在数组中
    la = x.size(); lb = y.size(); lc = la + lb;
    for(int i = 0; i < la; i++) a[la - 1 - i] = x[i] - '0';
    for(int i = 0; i < lb; i++) b[lb - 1 - i] = y[i] - '0';

    // 2. 模拟乘法的过程
    mul(c, a, b); // c = a * b

    // 输出结果
    for(int i = lc - 1; i >= 0; i--) cout << c[i];
    return 0;
}

复杂度分析

  • 时间复杂度:O(la × lb),双重循环逐位相乘。
  • 空间复杂度:O(la + lb),存储两个乘数与结果数组。

易错点

  1. 结果的最大位数是 la + lb,所以 lc = la + lb
  2. 无进位累加,全部乘完后再统一处理进位;
  3. 处理前导零,避免输出如 "0001" 这样的结果。

2.4 高精度除法

题目描述

输入两个整数 a, b,输出它们的商。

输入描述

两行,第一行是被除数,第二行是除数。

输出描述

一行,商的整数部分。

示例

复制代码
输入:
10
2

输出:
5

解题思路

模拟小学「列竖式」计算「两数相除」的过程(注意:这里是「高精度 ÷ 低精度」)。

  1. 定义一个指针 i 从高位遍历被除数,一个变量 t 标记当前「被除数的数」,记除数是 b;
  2. 更新当前被除数的数 t = t * 10 + a[i]
  3. t / b 表示这一位的商,t % b 表示这一位的余数;
  4. 用 t 记录这一次的余数,遍历到下一位的时候重复上面的过程;
  5. 被除数遍历完毕之后,t 里面存的就是余数,但是商可能存在前导 0,注意清空。

C++ 参考代码

cpp 复制代码
#include <iostream>

using namespace std;

const int N = 1e6 + 10;

typedef long long LL;

int a[N], b, c[N];
int la, lc;

// 高精度除法的模板 - c = a / b(高精度 / 低精度)
void sub(int c[], int a[], int b)
{
    LL t = 0; // 标记每次除完之后的余数
    for(int i = la - 1; i >= 0; i--)
    {
        // 计算当前的被除数
        t = t * 10 + a[i];
        c[i] = t / b;
        t %= b;
    }

    // 处理前导 0
    while(lc > 1 && c[lc - 1] == 0) lc--;
}

int main()
{
    string x; cin >> x >> b;

    la = x.size();

    for(int i = 0; i < la; i++) a[la - 1 - i] = x[i] - '0';

    // 模拟除法的过程
    lc = la;
    sub(c, a, b); // c = a / b

    for(int i = lc - 1; i >= 0; i--) cout << c[i];
    return 0;
}

复杂度分析

  • 时间复杂度:O(la),从高位到低位扫描一遍。
  • 空间复杂度:O(la),存储被除数与商。

易错点

  1. 除法要从高位往低位遍历(与加减乘的逆序方向相反);
  2. 余数要用 long long 保存(t = t * 10 + a[i] 可能很大);
  3. 商可能存在前导 0,最后统一去掉,但要保留至少 1 位。

下一篇:算法基础篇 · 03 枚举(C++ 题解)

相关推荐
6Hzlia1 小时前
【Classic 150 刷题计划】 LeetCode 242. 有效的字母异位词 | C++ 哈希计数与严密防线
c++·算法·leetcode
wabs6661 小时前
关于二叉树【力扣101.对称二叉树的思考】
数据结构·c++·算法·leetcode·二叉树
白远山1 小时前
无人自助健身平台搭建:从架构设计到设备联动的完整实战
java·开发语言·架构·需求分析
6Hzlia1 小时前
【Classic 150 刷题计划】 LeetCode 228. 汇总区间 | C++ 双游标区间扫描与 to_string 规范
c++·算法·leetcode
m4Rk_1 小时前
【论文阅读】Agent 记忆机制(72):EMA——在写入前决定什么值得记住
论文阅读·人工智能·学习·开源·github
扶风ff1 小时前
练题簿企业培训方案:统一题库、在线任务与岗位考核
学习·小程序
动词ing2 小时前
【学习笔记】数据结构 二分查找(二分查找模板+边界处理+查找区间+旋转数组)
笔记·学习
kkkkkkkkkk_Z2 小时前
学嵌入式和Linux应用编程|学习日记:深入理解TCP协议与网络编程实战
linux·笔记·学习
君顾12 小时前
本地电竞服务交易系统架构设计与实战:从同城服务撮合到订单履约
java·开发语言·电竞