JS/TS实现6位小写字母串与序号双向映射 无数组二分查找方案
实现核心原理
这个序列本质是固定6位长度的26进制数序列,字符a到z刚好对应进制位取值0到25,序号从0开始计数时,和常规26进制与十进制的转换逻辑完全对齐,不需要生成任何实体数组,双向映射的时间复杂度为常数级O(6),性能极高。
总元素量26^6 = 308915776,数值远小于JS的最大安全整数(2^53-1),直接用普通Number类型计算即可,不需要引入BigInt。
具体实现步骤
1. 实现序号转字符串的映射函数
核心逻辑是把十进制序号逐位除以26取余数,从最低位(对应字符串最右侧字符)开始换算成对应字母,最后补全为6位长度即可。
const TOTAL = 26 ** 6; const CHAR_CODE_A = 'a'.charCodeAt(0); function indexToStr(index) { if (index < 0 || index >= TOTAL) { throw new RangeError('序号超出0~26^6-1的合法范围'); } const result = []; let cur = index; for (let i = 0; i < 6; i++) { const remainder = cur % 26; result.unshift(String.fromCharCode(CHAR_CODE_A + remainder)); cur = Math.floor(cur / 26); } return result.join(''); }
校验逻辑:indexToStr(0)返回aaaaaa,indexToStr(25)返回aaaaaz,indexToStr(26)返回aaaaba,indexToStr(TOTAL - 1)返回zzzzzz,符合序列要求。
2. 实现字符串转序号的映射函数
核心逻辑是把字符串每一位换算成0-25的数值,按照26进制的位权累加,得到对应的十进制序号。
function strToIndex(str) { if (typeof str !== 'string' || str.length !== 6 || !/^[a-z]{6}$/.test(str)) { throw new TypeError('输入必须是6位小写字母组成的字符串'); } let index = 0; for (let i = 0; i < 6; i++) { const num = str.charCodeAt(i) - CHAR_CODE_A; index = index * 26 + num; } return index; }
两个函数为严格双向映射,任意合法输入经过两次转换后会得到原值。
3. 实现无数组二分查找
有了双向映射能力后,不需要构建实体数组,直接用序号作为左右边界即可完成二分查找。JS原生字符串的字典序比较结果,和序号大小的比较结果完全一致,不需要额外写比较逻辑。
function searchTarget(target) { if (typeof target !== 'string' || target.length !== 6 || !/^[a-z]{6}$/.test(target)) { throw new TypeError('查找目标必须是6位小写字母组成的字符串'); } let left = 0; let right = TOTAL - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); const midStr = indexToStr(mid); if (midStr === target) { return mid; } else if (midStr < target) { left = mid + 1; } else { right = mid - 1; } } // 全排列场景下所有合法输入必然存在,此处仅为逻辑兜底 return -1; }
性能与注意事项
- 两个转换函数固定执行6次循环,单次执行耗时在纳秒级;二分查找最多迭代
log2(308915776) ≈ 28次,Node.js环境下单次查找总耗时不到1毫秒,无额外内存占用,完全规避了生成3亿元素数组带来的内存溢出问题。 - 若业务侧需要序号从1开始计数,只需要在两个转换函数的入参、返回值位置统一做+1/-1的偏移即可,核心逻辑不需要改动。
- 必须加边界校验,避免非法入参导致计算结果偏差。
内容的提问来源于stack exchange,提问作者Michael Merjanov
相关产品推荐
相关产品推荐

