0168-Excel表列名称

Raphael Liu Lv10

给你一个整数 columnNumber ,返回它在 Excel 表中相对应的列名称。

例如:

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

示例 1:

**输入:** columnNumber = 1
**输出:** "A"

示例 2:

**输入:** columnNumber = 28
**输出:** "AB"

示例 3:

**输入:** columnNumber = 701
**输出:** "ZY"

示例 4:

**输入:** columnNumber = 2147483647
**输出:** "FXSHRXW"

提示:

  • 1 <= columnNumber <= 231 - 1

方法一:数学

在考虑如何将列序号转换成列名称之前,先考虑相反的问题,即如何将列名称转换成列序号。读者可以参考「171. Excel表列序号的官方题解 」。

引用该题解的结论,如果列名称的长度为 $n$,每一位对应的序号为 $[a_{n-1}, a_{n-2}, \ldots, a_0]$,其中对于任意 $0 \le i < n$ 都有 $1 \le a_i \le 26$,则列名称对应的列序号为:

$$
\textit{number} = \sum_{i=0}^{n-1} a_i \times 26^i
$$

将列序号转换成列名称,则是在已知 $\textit{number}$ 的情况下,解出 $a_0$ 到 $a_{n-1}$ 的值。

分离出 $a_0$ 项,提出其余项的公因数 $26$,上式可以改写为:

$$
\textit{number} = a_0 + 26 \times \sum_{i=1}^{n-1} a_i \times 26^{i-1}
$$

将等式两边同时减 $1$,得:

$$
\textit{number} - 1 = (a_0 - 1) + 26 \times \Big(\sum_{i=1}^{n-1} a_i \times 26^{i-1}\Big)
$$

由于 $0 \le a_0 - 1 \le 25$,由上式可知,$a_0 - 1$ 是 $\textit{number} - 1$ 除以 $26$ 的余数。

这样我们就得到了 $a_0$ 的值。

在得到 $a_0$ 的值之后,令 $\textit{number}’ = \dfrac{\textit{number} - a_0}{26}$,则有:

$$
\textit{number}’ = \sum_{i=1}^{n-1} a_i \times 26^{i-1} = a_1 + 26 \times \sum_{i=2}^{n-1} a_i \times 26^{i-2}
$$

于是使用同样的方法,可以得到 $a_1$ 的值。

上述过程是一个循环的过程,直至 $\textit{number}’=0$ 时停止。此时我们就得到了 $a_0$ 到 $a_{n-1}$ 的值。拼接这些值对应的字母,即得到了答案。

代码实现时,由于我们计算列名称的顺序是从右往左,因此需要将拼接后的结果反转。

[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
string convertToTitle(int columnNumber) {
string ans;
while (columnNumber > 0) {
int a0 = (columnNumber - 1) % 26 + 1;
ans += a0 - 1 + 'A';
columnNumber = (columnNumber - a0) / 26;
}
reverse(ans.begin(), ans.end());
return ans;
}
};
[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
class Solution {
public String convertToTitle(int columnNumber) {
StringBuffer sb = new StringBuffer();
while (columnNumber > 0) {
int a0 = (columnNumber - 1) % 26 + 1;
sb.append((char)(a0 - 1 + 'A'));
columnNumber = (columnNumber - a0) / 26;
}
return sb.reverse().toString();
}
}
[sol1-C#]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Solution {
public string ConvertToTitle(int columnNumber) {
StringBuilder sb = new StringBuilder();
while (columnNumber > 0) {
int a0 = (columnNumber - 1) % 26 + 1;
sb.Append((char)(a0 - 1 + 'A'));
columnNumber = (columnNumber - a0) / 26;
}
StringBuilder columnTitle = new StringBuilder();
for (int i = sb.Length - 1; i >= 0; i--) {
columnTitle.Append(sb[i]);
}
return columnTitle.ToString();
}
}
[sol1-JavaScript]
1
2
3
4
5
6
7
8
9
10
var convertToTitle = function(columnNumber) {
let ans = [];
while (columnNumber > 0) {
const a0 = (columnNumber - 1) % 26 + 1;
ans.push(String.fromCharCode(a0 - 1 + 'A'.charCodeAt()));
columnNumber = Math.floor((columnNumber - a0) / 26);
}
ans.reverse();
return ans.join('');
};
[sol1-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
func convertToTitle(columnNumber int) string {
ans := []byte{}
for columnNumber > 0 {
a0 := (columnNumber-1)%26 + 1
ans = append(ans, 'A'+byte(a0-1))
columnNumber = (columnNumber - a0) / 26
}
for i, n := 0, len(ans); i < n/2; i++ {
ans[i], ans[n-1-i] = ans[n-1-i], ans[i]
}
return string(ans)
}
[sol1-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void reverse(char* str, int strSize) {
int left = 0, right = strSize - 1;
while (left < right) {
char tmp = str[left];
str[left] = str[right], str[right] = tmp;
left++;
right--;
}
}

char* convertToTitle(int columnNumber) {
char* ans = malloc(sizeof(char) * 8);
int ansSize = 0;
while (columnNumber > 0) {
int a0 = (columnNumber - 1) % 26 + 1;
ans[ansSize++] = a0 - 1 + 'A';
columnNumber = (columnNumber - a0) / 26;
}
ans[ansSize] = '\0';
reverse(ans, ansSize);
return ans;
}
[sol1-Python3]
1
2
3
4
5
6
7
8
class Solution:
def convertToTitle(self, columnNumber: int) -> str:
ans = list()
while columnNumber > 0:
a0 = (columnNumber - 1) % 26 + 1
ans.append(chr(a0 - 1 + ord("A")))
columnNumber = (columnNumber - a0) // 26
return "".join(ans[::-1])

对于整数 $n$,若 $n$ 能被 $26$ 整除,则有:

$$
\dfrac{n}{26} = \Big\lfloor \dfrac{n+r}{26} \Big\rfloor
$$

其中 $0\le r \le 25$。

因此有:

$$
\begin{aligned}
\textit{number}’ &= \dfrac{\textit{number} - a_0}{26}\
&= \Big\lfloor \dfrac{(\textit{number}-a_0)+(a_0-1)}{26} \Big\rfloor\
&= \Big\lfloor \dfrac{\textit{number}-1}{26} \Big\rfloor
\end{aligned}
$$

这里我们用到了 $0 \le a_0 - 1 \le 25$ 这一性质。

据此,我们得到另外一种简洁的写法:

[sol2-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public:
string convertToTitle(int columnNumber) {
string ans;
while (columnNumber > 0) {
--columnNumber;
ans += columnNumber % 26 + 'A';
columnNumber /= 26;
}
reverse(ans.begin(), ans.end());
return ans;
}
};
[sol2-Java]
1
2
3
4
5
6
7
8
9
10
11
class Solution {
public String convertToTitle(int columnNumber) {
StringBuffer sb = new StringBuffer();
while (columnNumber != 0) {
columnNumber--;
sb.append((char)(columnNumber % 26 + 'A'));
columnNumber /= 26;
}
return sb.reverse().toString();
}
}
[sol2-C#]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Solution {
public string ConvertToTitle(int columnNumber) {
StringBuilder sb = new StringBuilder();
while (columnNumber != 0) {
columnNumber--;
sb.Append((char)(columnNumber % 26 + 'A'));
columnNumber /= 26;
}
StringBuilder columnTitle = new StringBuilder();
for (int i = sb.Length - 1; i >= 0; i--) {
columnTitle.Append(sb[i]);
}
return columnTitle.ToString();
}
}
[sol2-JavaScript]
1
2
3
4
5
6
7
8
9
var convertToTitle = function(columnNumber) {
const sb = [];
while (columnNumber !== 0) {
columnNumber--;
sb.push(String.fromCharCode(columnNumber % 26 + 'A'.charCodeAt()));
columnNumber = Math.floor(columnNumber / 26);
}
return sb.reverse().join('');
};
[sol2-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
func convertToTitle(columnNumber int) string {
ans := []byte{}
for columnNumber > 0 {
columnNumber--
ans = append(ans, 'A'+byte(columnNumber%26))
columnNumber /= 26
}
for i, n := 0, len(ans); i < n/2; i++ {
ans[i], ans[n-1-i] = ans[n-1-i], ans[i]
}
return string(ans)
}
[sol2-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void reverse(char* str, int strSize) {
int left = 0, right = strSize - 1;
while (left < right) {
char tmp = str[left];
str[left] = str[right], str[right] = tmp;
left++;
right--;
}
}

char* convertToTitle(int columnNumber) {
char* ans = malloc(sizeof(char) * 8);
int ansSize = 0;

while (columnNumber > 0) {
--columnNumber;
ans[ansSize++] = columnNumber % 26 + 'A';
columnNumber /= 26;
}
ans[ansSize] = '\0';
reverse(ans, ansSize);
return ans;
}
[sol2-Python3]
1
2
3
4
5
6
7
8
class Solution:
def convertToTitle(self, columnNumber: int) -> str:
ans = list()
while columnNumber > 0:
columnNumber -= 1
ans.append(chr(columnNumber % 26 + ord("A")))
columnNumber //= 26
return "".join(ans[::-1])

复杂度分析

  • 时间复杂度:$O(\log_{26} \textit{columnNumber})$。时间复杂度即为将 $\textit{columnNumber}$ 转换成 $26$ 进制的位数。

  • 空间复杂度:$O(1)$。返回值不计入空间复杂度。注意,在某些语言(比如 Java、C#、JavaScript)中字符串是不可变的,因此我们需要额外的 $O(\log_{26} \textit{columnNumber})$ 的空间来存储返回值的中间结果。但是我们忽略这一复杂度分析,因为这依赖于语言的细节。

 Comments
On this page
0168-Excel表列名称