自描述数是指一个n位数,其第k位(从0开始)等于数字k在该数中出现的次数;需满足位数n且每位数字∈[0,n−1],如1210是合法的4位自描述数。

什么是异构数?先确认定义再写代码
异构数(Autobiographical number)不是常见术语,容易和自描述数(self-descriptive number)混淆。实际编程中,用户想判断的通常是「自描述数」:一个 n 位数,其第 k 位(从左往右,索引从 0 开始)恰好等于该数字中数字 k 出现的次数。
例如 1210 是 4 位自描述数:
- 第 0 位是 1 → 数字 0 出现了 1 次(确实只有末尾一个 0)
- 第 1 位是 2 → 数字 1 出现了 2 次(两个 1)
- 第 2 位是 1 → 数字 2 出现了 1 次(中间那个 2)
- 第 3 位是 0 → 数字 3 出现了 0 次(没出现)
注意:严格意义下,自描述数只存在于特定长度(如 4 位、10 位),且必须满足位数 = 基数(即 n 位数只能含数字 0 到 n−1)。
用 string 处理比直接取模更安全
对整数做各位计数时,若用 % 10 / / 10 循环,会丢失前导零信息,且负数、0 的边界处理麻烦。转成 std::string 直观可靠:
- 先检查是否为非负整数,负数直接返回
false - 用
std::to_string(n)转字符串,得到位数len - 若任意字符不在
'0'到'0'+len-1范围内,直接 false - 构建频次数组
count[10] = {0},遍历字符串统计各数字出现次数 - 再遍历字符串,检查
s[i]对应的数字是否等于count[i]
关键校验点:长度与数字范围必须匹配
自描述数的数学约束很强,很多看似合理的数其实不合法:
立即学习“C++免费学习笔记(深入)”;
-
1210合法(4 位,只含 0~3) -
2020不合法:第 0 位是2,但数字0实际出现了 2 次 ✅;第 1 位是0,但数字1出现了 0 次 ✅;第 2 位是2,但数字2出现了 2 次 ✅;第 3 位是0,但数字3出现了 0 次 ✅ → 等等,它其实是合法的(4 位自描述数还有这个) -
123直接拒绝:3 位数却出现了3(超出 0~2 范围) -
1000:长度 4,但第 0 位说「0 出现 1 次」,实际有三个0→ 不匹配
所以必须在统计前就做范围校验:s[i] - '0' >= len 就立刻 return false。
完整可运行判断函数
bool isSelfDescriptive(int n) {
if (n < 0) return false;
std::string s = std::to_string(n);
int len = s.size();
std::vector<int> count(10, 0); // 最多统计 0~9
<pre class='brush:php;toolbar:false;'>// 先验证每位数字是否越界
for (char c : s) {
int d = c - '0';
if (d >= len) return false;
count[d]++;
}
// 再核对每一位描述是否准确
for (int i = 0; i < len; ++i) {
int expected = s[i] - '0';
if (count[i] != expected) return false;
}
return true;}
注意:该函数不处理前导零输入(如 "0012"),因为 std::to_string(12) 永远不会产生前导零。如果需要支持字符串输入,去掉 int 参数,直接传 const std::string& s 并额外校验是否为空或含非数字字符。
真正容易被忽略的是位数与数字值域的绑定关系——不是所有“每位数字都在 0~9 之间”的数都值得继续算频次,必须第一时间筛掉超范围的位。


















