图 1:本文封面,A=1、Z=26、AA=27 这张小表就是全部题意。
先说明属性:这是一篇历史题目复盘,题目出自本地历史面试资料,与任何公司的当前招聘无关。这类「字符串和序号互转」的设计题,本质是考察你能不能认出它是一张换皮的进制转换卷。
想象面试现场:「设计两个算法:把 A、B、…、Z、AA、AB 这样的字符串映射成 1、2、…、26、27、28 这样的序号,还能转回来。说说你的思路和边界情况。」
第一步,点破本质:这是二十六进制,但没有 0。
从左到右读字符串,每一位的权值是 26 的幂,这和十进制、十六进制是同一套结构。差别只有一处:常见进制的每一位取值是 0 到 25(或 0 到 9),而这套字母体系的每一位取值是 1 到 26——没有 0。A 是 1,Z 是 26,AA 是 27。没有 0 这一点看着小,却是本题全部的坑所在:编码方向每取一位,都要先做一步「余 0 当 26、高位借 1」的修正。
第二步,两个方向的算法。
解码(字符串到序号)是简单方向:从左到右扫,结果等于结果乘 26 加上当前字母的值。以 AB 为例:1 乘 26 加 2,得 28。
编码(序号到字符串)是麻烦方向:反复对序号减 1、除 26 取余,余数映射回字母,从低位向高位逐位生成,直到商为 0。为什么要先减 1?因为没有 0 的进制里,「整除」不代表这一位是 0,而是代表这一位是 26(Z),同时高位要被借走 1。先减 1 再取余,就把借位动作一次性做掉了。
第三步,写出可运行代码并实测。
下面这份实现包含两个方向的互转、经典样例、一百万次往返自验,以及一个对照变体——短网址风格的 62 进制编码(有 0,不需要借位)。用 Node.js 22.15.0 直接运行(node excel.js)即可:
'use strict';
// 字符串 -> 序号:二十六进制累乘(A=1 … Z=26)
function titleToNumber(s) {
let n = 0;
for (const ch of s) n = n * 26 + (ch.charCodeAt(0) - 64);
return n;
}
// 序号 -> 字符串:没有 0 的进制,每位先「减 1 借位」再取余
function numberToTitle(n) {
let s = '';
while (n > 0) {
n -= 1; // 余 0 当 26 用,高位要借 1
s = String.fromCharCode(65 + (n % 26)) + s;
n = Math.floor(n / 26);
}
return s;
}
// 经典样例
for (const s of ['A', 'Z', 'AA', 'AB', 'AZ', 'BA', 'ZY', 'ZZ', 'AAA']) {
console.log(`${s} -> ${titleToNumber(s)}`);
}
// 往返自验:1 到 100 万,编码再解码必须回到原数
let bad = 0;
for (let i = 1; i <= 1000000; i++) {
if (titleToNumber(numberToTitle(i)) !== i) bad++;
}
console.log('1..1000000 往返自验,失败数:', bad);
// 同构变体:短网址常用的 62 进制(0-9 a-z A-Z),因为有 0,不需要借位
const ALPHA62 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
function idToShort(id) {
let s = '';
while (id > 0) { s = ALPHA62[id % 62] + s; id = Math.floor(id / 62); }
return s || '0';
}
function shortToId(s) {
let n = 0;
for (const ch of s) n = n * 62 + ALPHA62.indexOf(ch);
return n;
}
console.log('短编码示例: 123456789 ->', idToShort(123456789), '-> 解码回', shortToId(idToShort(123456789)));
实测输出如下:
A -> 1
Z -> 26
AA -> 27
AB -> 28
AZ -> 52
BA -> 53
ZY -> 701
ZZ -> 702
AAA -> 703
1..1000000 往返自验,失败数: 0
短编码示例: 123456789 -> 8m0Kx -> 解码回 123456789
样例全对,一百万次往返零失败;最后两行顺手证明了同一件事:短网址把数字 ID 编成 62 进制短串,Excel 列号是 26 进制,模型词表里 token id 与文本互转——全是「字符串↔序号」这同一个映射换了个字母表。
图 2:进制本质、两个方向的算法与同构应用。
可能的追问与易错点。
「输入校验呢?」要约定非法字符、空串、序号为 0 或负数的行为——映射定义在 1 到正无穷上,0 没有对应字符串。「序号很大怎么办?」十进制序号乘 26 会涨得很快,要确认目标语言的整数精度:JavaScript 的安全整数上限约 9 千万亿,超长字符串需要换大整数类型。易错点集中在编码方向:忘记先减 1,遇到 26 的倍数(Z、AZ、ZZ)就会错一位;或者把生成顺序搞反,拼出的字符串低位在前。自验也有一句方法论:这种互转题最硬的验收标准就是大规模往返测试,比盯着边界样例看半天可靠得多。
回到面试现场,建议的答题节奏:先用十进制类比点破「这是进制转换」,再指出「没有 0」这个差异,解码方向随手写出,编码方向重点讲借位修正,最后用往返自验收尾。认出进制,题目就解了一半——另一半是细节,而细节正是面试要看的部分。
你第一次发现某道面试题「其实是另一道题的换皮」是什么时候?欢迎分享。