数位拆分:把一个数拆成一位一位¶
什么时候用?¶
题目说“个位、十位、各位数字之和、几位数、某个数字出现几次”,可以想到数位拆分。
先会:整数除法与取余、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 选数,内层拆当前数 |
明确是三位非负整数时,代码片段如下:
for 也能拆数位,下面是等价思路(代码片段,n > 0,sum 已初始化为 0):
所以不是“拆数位禁止用 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。
动手练习¶
5070的个位是多少?去掉个位之后是多少?- 为什么不能用
n % 10 == 0作为拆分结束条件? - 输入 5 个数,都要求位数:哪一个循环负责“5 个”,哪一个负责“每一位”?
答案
- 分别是
0和507。 - 末尾为
0不代表前面没有数字,例如5070。 - 外层
for负责 5 个数,内层拆数位;每处理一个新数,要重新初始化位数计数器。
下一步:L 到 R 中有多少个 2、回文判断。