跳转至

数位拆分:把一个数拆成一位一位

什么时候用?

题目说“个位、十位、各位数字之和、几位数、某个数字出现几次”,可以想到数位拆分。

先会:整数除法与取余、while 循环。本页处理非负整数,不直接套到负数上。

两个关键动作

对非负整数 x:x % 10 取出个位,x /= 10 去掉个位。拆出来的顺序是从右往左。

例如 x = 3042:

当前 x x % 10:取出的数字 x / 10:去掉个位后
3042 2 304
304 4 30
30 0 3
3 3 0

中间的 0 也是一位,不能跳过!

完整例子:求位数和数位和

输入 0 <= n <= 10^9,输出位数和各位数字之和。

#include <iostream>
using namespace std;

int main() {
    long long n;
    cin >> n;
    long long x = n; // 拆副本,保留原来的 n
    int count = 0, sum = 0;
    do {
        int digit = x % 10;
        count++;
        sum += digit;
        x /= 10;
    } while (x > 0);
    cout << count << ' ' << sum << '\n';
    return 0;
}

输入 3042,输出 4 9;输入 0,输出 1 0。

这里用 do while,因为它至少执行一次:0 本身也是一位数。如果还没学它,可以用普通 while (x > 0),并单独处理 n == 0 的情况。

x /= 10 不能漏掉,否则 x 不变,循环可能永远不停。

要不要使用 for 循环?

先问:我是在处理“一个数的数位”,还是“很多个数”?不要只看题目里有没有数字 n。

要做的事 容易理解的写法
只取一个数的个位 n % 10,不用循环
明确给一个三位数,取百、十、个位 直接写三个算式,不用循环
位数不固定,要处理每一位 while 或 do while 拆到结束
输入 n 个整数,每个都要求数位和 外层 for 读 n 次,内层拆当前数
检查 L 到 R 的每个整数 外层 for 选数,内层拆当前数

明确是三位非负整数时,代码片段如下:

int hundreds = n / 100;
int tens = n / 10 % 10;
int ones = n % 10;

for 也能拆数位,下面是等价思路(代码片段,n > 0,sum 已初始化为 0):

for (long long x = n; x > 0; x /= 10) {
    sum += x % 10;
}

所以不是“拆数位禁止用 for”,而是选自己最能看懂、不容易写错的循环。上面的 for 对 n = 0 不执行;求位数或统计数字 0 时要特别注意。

反转一个整数

把 123 反过来得到 321。每取出一位,就把它放到新数的右边:

reversed = reversed * 10 + digit。

例如取出的数字依次是 3、2、1,新数依次变成 3、32、321。

本例限定 0 <= n <= 10^9,用 long long 保存反转结果,避免 32 位 int 装不下某些反转后的数。

#include <iostream>
using namespace std;

int main() {
    long long n;
    cin >> n;
    long long x = n, reversed = 0;
    while (x > 0) {
        reversed = reversed * 10 + x % 10;
        x /= 10;
    }
    cout << reversed << '\n';
    return 0;
}

输入 1200,输出 21,不是 0021:整数不会保存前导零。输入 0,输出 0。

如果题目要保留原来的每一个字符,例如把 1200 输出成 0021,应按字符串处理。若数有几十位,也不能硬塞进 long long。

动手练习

  1. 5070 的个位是多少?去掉个位之后是多少?
  2. 为什么不能用 n % 10 == 0 作为拆分结束条件?
  3. 输入 5 个数,都要求位数:哪一个循环负责“5 个”,哪一个负责“每一位”?
答案
  1. 分别是 0 和 507。
  2. 末尾为 0 不代表前面没有数字,例如 5070。
  3. 外层 for 负责 5 个数,内层拆数位;每处理一个新数,要重新初始化位数计数器。

下一步:L 到 R 中有多少个 2、回文判断。