☰
doocs/leetcode 精讲:168. Excel 表列名称——「无零」26 进制转换的原理推导与六语言实现
2026/10/18 1:34:28 网站建设 项目流程
  • 示例工程

【免费下载链接】leetcode

🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解

项目地址:https://gitcode.com/doocs/leetcode
点击查看免费下载

本文基于 doocs/leetcode 仓库中 0168.Excel Sheet Column Title 英文题解(中文题解)展开。这道题表面是「进制转换」,实则是数字系统(number system)中的一个经典陷阱:Excel 列名使用的并不是我们熟悉的普通 26 进制,而是一种没有 0 数字的「双射 26 进制」(bijective base-26)。读完本文,你将掌握:为什么26 -> "Z"而不是"A0"、如何用「先减一再取模」的统一写法规避余 0 问题,以及 Python3 / Java / Go / TypeScript / Rust / C# 六种语言的实现差异与边界处理。

一、题目回顾:整数到列名的映射规律

题目要求:给定整数columnNumber(取值范围1 <= columnNumber <= 2^31 - 1),返回它在 Excel 表格中对应的列名称。

映射规律如下:

A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ...

题目给出的三个标准示例:

输入columnNumber输出
1"A"
28"AB"
701"ZY"

此外,仓库中的中文题解还给出了一个最大边界示例,便于验证算法在 32 位整数上限处的正确性:

输入columnNumber输出
2147483647(即 2^31 - 1)"FXSHRXW"

二、核心难点:Excel 列名不是普通 26 进制

这是本题最容易踩坑的地方。如果按照「普通 26 进制」来思考,A~Z应该对应0~25,那么26会被写成"A0"、27写成"A1"……但这显然与题目给出的映射Z -> 26、AA -> 27矛盾。

正确理解是:Excel 列名是一个「没有 0」的进制系统,26 个字母A~Z充当的是1~26这 26 个「非零数字」,而不是0~25:

  • 一位数:A(1) ~ Z(26);
  • 两位数:AA(27) ~ ZZ(702);
  • 三位数:AAA(703) ~ ZZZ(18278),以此类推。

原题解中的思考(thinking)段落对此有精辟总结:"Ordinary base 26 would emit 0 instead of Z on a zero remainder"——普通 26 进制在余数为 0 时会输出0,而这里必须输出Z。因此核心解法是:每次先减 1,再对 26 取模,把余数映射到A~Z,商继续迭代,最后反转字符序列。

三、标准解法:先减一再取模(双射 26 进制)

算法流程(伪代码):

while columnNumber > 0: columnNumber = columnNumber - 1 当前字符 = 'A' + columnNumber % 26 将当前字符加入结果(头部插入或最后统一反转) columnNumber = columnNumber // 26 反转结果,返回

为什么「先减一」能解决问题?因为在双射进制中,数字范围是1~26,而取模运算天然工作在0~25上。减一操作把数字空间平移一位,使余数0~25恰好对应A~Z,同时商也相应调整,保证进位边界(如26 -> Z、27 -> AA)正确。

逐步推演示例:columnNumber = 701(应输出"ZY")

  1. 701 - 1 = 700,700 % 26 = 24→ 字符'A' + 24 = 'Y';700 // 26 = 26;
  2. 26 - 1 = 25,25 % 26 = 25→ 字符'A' + 25 = 'Z';25 // 26 = 0,循环结束;
  3. 逆序拼接得到"ZY"。✔

四、多语言实现与源码对照

本仓库为本题提供了独立的 Solution 源码目录,包含 Python3、Java、Go、TypeScript、Rust、C# 六个实现文件,与题解文档中的代码一一对应,可直接运行验证。

4.1 Python3

对应源码:Solution.py

class Solution: def convertToTitle(self, columnNumber: int) -> str: res = [] while columnNumber: columnNumber -= 1 res.append(chr(ord('A') + columnNumber % 26)) columnNumber //= 26 return ''.join(res[::-1])

要点:用列表收集每次计算出的字符(低位在前),最后通过res[::-1]一次反转完成拼接。chr(ord('A') + columnNumber % 26)将余数0~25映射到A~Z。

4.2 Java

对应源码:Solution.java

class Solution { public String convertToTitle(int columnNumber) { StringBuilder res = new StringBuilder(); while (columnNumber != 0) { --columnNumber; res.append((char) ('A' + columnNumber % 26)); columnNumber /= 26; } return res.reverse().toString(); } }

要点:StringBuilder天然支持reverse(),字符以char强转方式从'A' + 余数得到,最终调用res.reverse().toString()输出正确顺序。

4.3 Go

对应源码:Solution.go

func convertToTitle(columnNumber int) string { res := []rune{} for columnNumber != 0 { columnNumber -= 1 res = append([]rune{rune(columnNumber%26 + int('A'))}, res...) columnNumber /= 26 } return string(res) }

要点:这里采用头插法,append([]rune{...}, res...)把新字符插到切片头部,从而免去最后反转的步骤。rune类型用于安全处理 Unicode 字符,int('A')拿到A的 ASCII 码 65。

4.4 TypeScript

对应源码:Solution.ts

function convertToTitle(columnNumber: number): string { let res: string[] = []; while (columnNumber > 0) { --columnNumber; let num: number = columnNumber % 26; res.unshift(String.fromCharCode(num + 65)); columnNumber = Math.floor(columnNumber / 26); } return res.join(''); }

要点:String.fromCharCode(num + 65)用65(A的 ASCII 码)代替'A',效果等价;unshift为头插法;注意使用Math.floor处理整除,避免浮点精度问题。

4.5 Rust(特判写法)

对应源码:Solution.rs

impl Solution { #[allow(dead_code)] pub fn convert_to_title(column_number: i32) -> String { let mut ret = String::from(""); let mut column_number = column_number; while column_number > 0 { if column_number <= 26 { ret.push((('A' as u8) + (column_number as u8) - 1) as char); break; } else { let mut left = column_number % 26; left = if left == 0 { 26 } else { left }; ret.push((('A' as u8) + (left as u8) - 1) as char); column_number = (column_number - 1) / 26; } } ret.chars().rev().collect() } }

要点:这是与「先减一再取模」等价但写法不同的另一种实现。它显式处理余 0 情况:当column_number % 26 == 0时,把余数修正为26(即字母Z),同时用(column_number - 1) / 26得到下一轮商。当column_number <= 26时直接输出对应字母并终止循环,最后ret.chars().rev().collect()反转。两种思路殊途同归,Rust 版本更直观地体现了「余 0 补 26」的双射进制语义。

4.6 C#

对应源码:Solution.cs

public class Solution { public string ConvertToTitle(int columnNumber) { StringBuilder res = new StringBuilder(); while (columnNumber != 0) { --columnNumber; res.Append((char) ('A' + columnNumber % 26)); columnNumber /= 26; } return new string(res.ToString().Reverse().ToArray()); } }

要点:与 Java 版几乎一致的逻辑,差异仅在于 C# 的StringBuilder没有内建Reverse(),因此借助 LINQ 的Reverse()扩展方法配合ToArray()完成反转,再包装为string。

五、复杂度与边界用例分析

复杂度:每次循环columnNumber至少缩小 26 倍,因此:

  • 时间复杂度:O(log_26 columnNumber),即结果字符串的长度;
  • 空间复杂度:O(log_26 columnNumber),用于存储结果字符。

关键边界用例自检表(建议写代码时逐一验证):

输入输出说明
1"A"最小值,一位字母
26"Z"最重要的进位边界:余 0 场景,普通 26 进制会错算成"A0"
27"AA"进位后回到A开头
52"AZ"第二位是Z的场景
701"ZY"题目标准示例
702"ZZ"两位数最大值
703"AAA"进入三位数
2147483647"FXSHRXW"2^31 - 1,32 位整数上限

六、关联拓展:逆问题 171「Excel 表列序号」

理解本题的最好方式之一是研究它的逆运算:给定列名"AB",求列序号28。仓库中收录了 0171.Excel Sheet Column Number 题解,其核心递推式为:

ans = ans * 26 + (c - 'A' + 1)

即从左到右扫描字符,把每个字母看成双射 26 进制的一位数字(A~Z对应1~26),边读边累乘。例如"AB":ans = 1 * 26 + 2 = 28。该题解中明确写道:"Title to number is the inverse of problem 168"(列名转数字正是 168 题的逆运算),两个题解互为镜像,对比阅读可以彻底打通「双射进制」这一概念:168 题是「数字 → 字符串」的反复除 26,171 题是「字符串 → 数字」的反复乘 26。

七、小结

  • Excel 列名的本质是没有 0 数字的 26 进制(双射进制),字母A~Z对应1~26;
  • 统一解法:每轮先columnNumber -= 1再取模 26,余数映射到A~Z,商继续迭代,最后反转;
  • 仓库提供了 题解文档(含六个语言标签页)与 独立 Solution 源码 双重载体,Solution.rs还展示了「余 0 补 26」的等价特判写法;
  • 与逆问题 171. Excel Sheet Column Number 对照学习,可完整掌握这类「双射进制」转换题的两种方向。

掌握这道题后,你不仅能秒杀同类进制映射题,还能理解诸如「Excel 列号 ↔ 列名转换」「短网址 ID 混淆」等真实业务场景背后的同一个数学原理。

  • 示例工程

【免费下载链接】leetcode

🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解

项目地址:https://gitcode.com/doocs/leetcode
点击查看免费下载
上一篇:Wireshark终极指南:10个技巧掌握网络数据包嗅探与协议分析艺术
下一篇:老Mac如何免费装上新版macOS:OpenCore Legacy Patcher 完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询